数据通信流程:
通过selector.select()阻塞方法获取到感兴趣事件的key,根据key定位到channel,通过channel的读写操作进行数据通信。channel的read或者write操作都是通过buffer进行的。
代码示例
Server:
public class Server {
public static void main(String[] args) throws InterruptedException {
final int port = 9999;
try {
// 初始化server
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
Selector selector = Selector.open();
// 这一步的设置在register中需要用到,所以要在register之前设置,否则会发生异常
server.configureBlocking(false);
SelectionKey serverKey = server.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer = ByteBuffer.allocate(50);
// 事件循环
while(true) {
Thread.sleep(500);
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// System.out.println(serverKey == key);
continue;
}
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
}
if (key.isWritable()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.write(ByteBuffer.wrap("I am server!".getBytes()));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client:
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel client = SocketChannel.open();
client.connect(new InetSocketAddress("localhost", 9999));
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(50);
while (true) {
buffer.clear();
int i = client.read(buffer);
if (i > 0) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
}
client.write(ByteBuffer.wrap("I am a client!".getBytes()));
Thread.sleep(500);
}
}
}
其他
运行在Linux kernel 2.6以后的OS中的Java应用,NIO的底层实现是根据OS提供的io多路复用接口epoll实现。
参考资料
- 论文《Scalable IO in Java》:http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf (强烈推荐,NIO的源头)
- NIO基础教程:http://ifeve.com/java-nio-all
- C语言下的epoll使用:https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/