转载 本文介绍java网络IO方面的知识,包含BIO、NIO和AIO的使用例子。
1.IO 术语
BIO 同步阻塞IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理。
NIO 同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。
AIO 异步非阻塞IO,在此种模式下,用户进程只需要发起一个IO操作然后就立即返回,等IO操作真的完成以后,应用程序会得到IO操作完成的通知,此时用户程序对数据进行处理就好了,而不需要进行实际的IO读写操作,因为真正的IO读或写操作已经由内核完成。
上面涉及到两对词语,同步和异步,阻塞和非阻塞。 同步 指用户进程触发IO操作并等待IO操作是否就绪,通常使用轮询方法查看。 异步 指用户进程触发IO操作后就做自己的事情,当IO操作完成时会得到IO操作完成的通知,通常是把回调函数给通知者。 阻塞 指当读写文件操作符时,如果当时数据没有准备好或者不可读写,用户程序就进入等待状态,直到数据准备好或者可以读写为止。 非阻塞 相对于阻塞,当数据没有准备好或者不可读写,立刻返回而不等待。
2.代码示例
为了能更快的看到这些IO具体怎么使用,下面直接show code。
2.1 BIO
1.BIO server
package com.nio.example;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PlainEchoServer {
private static final ExecutorService executorPool = Executors.newFixedThreadPool(5);
private static class Handler implements Runnable {
private Socket clientSocket;
public Handler(Socket clientSocket){
this.clientSocket = clientSocket;
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
char chars[] = new char[64];
int len = reader.read(chars);
StringBuffer sb = new StringBuffer();
sb.append(new String(chars, 0, len));
System.out.println("From client: " + sb);
writer.write(sb.toString());
writer.flush();
} catch (IOException e) {
e.printStackTrace();
try {
clientSocket.close();
} catch (IOException ex) {
// ignore on close
}
}
}
}
public void serve(int port) throws IOException {
final ServerSocket socket = new ServerSocket(port);
try {
while (true) {
long beforeTime = System.nanoTime();
final Socket clientSocket = socket.accept();
System.out.println("Establish connection time: " + (System.nanoTime() - beforeTime) + " ns");
executorPool.execute(new Handler(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
PlainEchoServer server = new PlainEchoServer();
server.serve(8080);
}
}
2.BIO client
package com.nio.example;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.net.UnknownHostException;
public class PlainEchoClient {
public static void main(String args[]) throws Exception {
for (int i = 0; i < 1; i++) {// i,20
startClientThread(i + "name");
}
}
private static void startClientThread(String name) throws UnknownHostException, IOException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
startClient();
} catch (Exception e) {
e.printStackTrace();
}
}
}, name);
t.start();
}
private static void startClient() throws UnknownHostException, IOException {
long beforeTime = System.nanoTime();
String host = "127.0.0.1";
int port = 8080;
Socket client = new Socket(host, port);
// 建立连接后就可以往服务端写数据了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server. from: " + Thread.currentThread().getName());
writer.flush();
// 写完以后进行读操作
Reader reader = new InputStreamReader(client.getInputStream());
char chars[] = new char[64];// 假设所接收字符不超过64位,just for demo
int len = reader.read(chars);
StringBuffer sb = new StringBuffer();
sb.append(new String(chars, 0, len));
System.out.println("From server: " + sb);
writer.close();
reader.close();
client.close();
System.out.println("Client use time: " + (System.nanoTime() - beforeTime) + " ns");
}
}
2.2 NIO
1.NIO server
package com.stevex.app.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NIOServer {
private ByteBuffer readBuffer;
private Selector selector;
public static void main(String[] args) {
NIOServer server = new NIOServer();
server.init();
server.listen();
}
private void init() {
readBuffer = ByteBuffer.allocate(1024);
ServerSocketChannel servSocketChannel;
try {
servSocketChannel = ServerSocketChannel.open();
servSocketChannel.configureBlocking(false);
// 绑定端口
servSocketChannel.socket().bind(new InetSocketAddress(8383));
selector = Selector.open();
servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
private void listen() {
while (true) {
try {
selector.select();
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
ite.remove();// 确保不重复处理
handleKey(key);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
private void handleKey(SelectionKey key) throws IOException, ClosedChannelException {
SocketChannel channel = null;
try {
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
channel = serverChannel.accept();// 接受连接请求
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
channel = (SocketChannel) key.channel();
readBuffer.clear();
/*
* 当客户端channel关闭后,会不断收到read事件,但没有消息,即read方法返回-1 所以这时服务器端也需要关闭channel,避免无限无效的处理
*/
int count = channel.read(readBuffer);
if (count > 0) {
// 一定需要调用flip函数,否则读取错误数据
readBuffer.flip();
/*
* 使用CharBuffer配合取出正确的数据 String question = new String(readBuffer.array());
* 可能会出错,因为前面readBuffer.clear();并未真正清理数据 只是重置缓冲区的position, limit, mark,
* 而readBuffer.array()会返回整个缓冲区的内容。 decode方法只取readBuffer的position到limit数据。 例如,上一次读取到缓冲区的是"where",
* clear后position为0,limit为 1024, 再次读取“bye"到缓冲区后,position为3,limit不变,
* flip后position为0,limit为3,前三个字符被覆盖了,但"re"还存在缓冲区中, 所以 new String(readBuffer.array()) 返回 "byere",
* 而decode(readBuffer)返回"bye"。
*/
CharBuffer charBuffer = CharsetHelper.decode(readBuffer);
String question = charBuffer.toString();
String answer = getAnswer(question);
channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));
} else {
// 这里关闭channel,因为客户端已经关闭channel或者异常了
channel.close();
}
}
} catch (Throwable t) {
t.printStackTrace();
if (channel != null) {
channel.close();
}
}
}
private String getAnswer(String question) {
String answer = null;
switch (question) {
case "who":
answer = "我是小娜\n";
break;
case "what":
answer = "我是来帮你解闷的\n";
break;
case "where":
answer = "我来自外太空\n";
break;
case "hi":
answer = "hello\n";
break;
case "bye":
answer = "88\n";
break;
default:
answer = "请输入 who, 或者what, 或者where";
}
return answer;
}
}
2.NIO client
package com.stevex.app.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class NIOClient implements Runnable {
private BlockingQueue<String> words;
private Random random;
public static void main(String[] args) {
// 种多个线程发起Socket客户端连接请求
for (int i = 0; i < 10; i++) {
NIOClient c = new NIOClient();
c.init();
new Thread(c).start();
}
}
@Override
public void run() {
SocketChannel channel = null;
Selector selector = null;
try {
channel = SocketChannel.open();
channel.configureBlocking(false);
// 请求连接
channel.connect(new InetSocketAddress("localhost", 8383));
selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);
boolean isOver = false;
while (!isOver) {
selector.select();
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
ite.remove();
if (key.isConnectable()) {
if (channel.isConnectionPending()) {
if (channel.finishConnect()) {
// 只有当连接成功后才能注册OP_READ事件
key.interestOps(SelectionKey.OP_READ);
channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));
sleep();
} else {
key.cancel();
}
}
} else if (key.isReadable()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
channel.read(byteBuffer);
byteBuffer.flip();
CharBuffer charBuffer = CharsetHelper.decode(byteBuffer);
String answer = charBuffer.toString();
System.out.println(Thread.currentThread().getId() + "---" + answer);
String word = getWord();
if (word != null) {
channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));
} else {
isOver = true;
}
sleep();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void init() {
words = new ArrayBlockingQueue<String>(5);
try {
words.put("hi");
words.put("who");
words.put("what");
words.put("where");
words.put("bye");
} catch (InterruptedException e) {
e.printStackTrace();
}
random = new Random();
}
private String getWord() {
return words.poll();
}
private void sleep() {
try {
TimeUnit.SECONDS.sleep(random.nextInt(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.charset 编码
package com.stevex.app.nio;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public class CharsetHelper {
private static final String UTF_8 = "UTF-8";
private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
return encoder.encode(in);
}
public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
return decoder.decode(in);
}
}
2.3 AIO
1.AIO server
package com.aio.example;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class AIOSocketServer {
public static final int PORT = 8082;
public static final String HOST = "localhost";
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
new AIOSocketServer();
}
public AIOSocketServer() throws IOException, InterruptedException, ExecutionException {
// open a server channel and bind to a free address, then accept a connection
System.out.println("Open server channel");
SocketAddress address = new InetSocketAddress(HOST, PORT);
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(address);
System.out.println("Initiate accept");
Future<AsynchronousSocketChannel> future = server.accept();
// wait for the accept to finish
AsynchronousSocketChannel worker = future.get();
System.out.println("Accept completed");
ByteBuffer readBuffer = ByteBuffer.allocate(100);
try {
// read a message from the client, timeout after 10 seconds
worker.read(readBuffer).get(10, TimeUnit.SECONDS);
System.out.println("Message received from client: " + new String(readBuffer.array()));
// send a message to the client
ByteBuffer message = ByteBuffer.wrap("hello client, i am Alice.".getBytes());
worker.write(message);
} catch (TimeoutException e) {
System.out.println("Client didn't respond in time");
}
server.close();
}
}
2.AIO client
package com.aio.example;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
*
* @author macun 2015年11月19日 上午9:56:00
*/
public class AIOSocketClient {
public static final int PORT = 8082;
public static final String HOST = "localhost";
public static void main(String[] args) throws IOException {
// create a client
SocketAddress address = new InetSocketAddress(HOST, PORT);
ClientWrapper client = new ClientWrapper(address);
// start client thread
client.start();
try {
client.join();
} catch (InterruptedException e) {
System.out.println(e);
}
client.close();
}
public static class ClientWrapper extends Thread {
AsynchronousSocketChannel client;
Future<Void> connectFuture;
public ClientWrapper(SocketAddress server) throws IOException{
// open a new socket channel and connect to the server
System.out.println("Open client channel");
client = AsynchronousSocketChannel.open();
System.out.println("Connect to server");
connectFuture = client.connect(server);
}
public void run() {
System.out.println("client run.");
// if the connect hasn't happened yet cancel it
// if (!connectFuture.isDone()) {
// connectFuture.cancel(true);
// return;
// }
try {
connectFuture.get();
} catch (InterruptedException e1) {
System.out.println("client connect error." + e1);
return;
} catch (ExecutionException e1) {
System.out.println("client connect error." + e1);
return;
}
try {
// send a message to the server
ByteBuffer message = ByteBuffer.wrap("hello server, i am Bruce.".getBytes());
// wait for the response
System.out.println("Sending message to the server...");
Integer countBytes = client.write(message).get();
System.out.println(countBytes);
final ByteBuffer readBuffer = ByteBuffer.allocate(100);
// Future<Integer> numberBytes = client.read(readBuffer);
client.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("Message received from server: " + new String(readBuffer.array()));
clearUp();
}
@Override
public void failed(Throwable e, Object attachment) {
System.err.println("Exception performing write");
e.printStackTrace();
clearUp();
}
private void clearUp() {
try {
client.close();
} catch (IOException e) {
System.out.println(e);
}
}
});
} catch (InterruptedException e) {
System.out.println(e);
} catch (ExecutionException e) {
System.out.println(e);
}
}
public void close() throws IOException {
client.close();
}
}
}
3.三种IO适用场景建议
1.BIO:适用于连接数目比较小且固定的架构,对服务器资源要求比较高,并发局限于应用中。jdk1.4以前的唯一选择,程序直观简单。 2.NIO:适用于连接数目较多且连接较短的应用,比如聊天服务器,并发局限于应用中,编程相对复杂,jdk1.4后开始支持,jetty就是采用的NIO。 3.AIO:适用于连接数目较多且连接较长的应用,比如相册服务器,充分调用OS参与并发操作,编程比复杂,jdk1.7开始支持。
4.IO方面推荐文章 1.深入分析java I/O 的工作机制