前言
Java NIO 由以下几个核心部分组成:
Buffer
Channel
Selector
以前基于net包进行socket编程时,accept方法会一直阻塞,直到有客户端请求的到来,并返回socket进行相应的处理。整个过程是流水线的,处理完一个请求,才能去获取并处理后面的请求;当然我们可以把获取socket和处理socket的过程分开,一个线程负责accept,线程池负责处理请求。
NIO为我们提供了更好的解决方案,采用选择器(Selector)找出已经准备好读写的socket,并按顺序处理,基于通道(Channel)和缓冲区(Buffer)来传输和保存数据。
Buffer和Channel已经介绍过深入浅出NIO Channel和Buffer,本文主要介绍NIO的Selector和Socket的实践以及实现原理。
Selector是什么?
在养鸡场,有这一个人,每天的工作就是不停检查几个特殊的鸡笼,如果有鸡进来,有鸡出去,有鸡生蛋,有鸡生病等等,就把相应的情况记录下来。这样,如果负责人想知道鸡场情况,只需要到那个人查询即可,当然前提是,负责得让那个人知道需要记录哪些情况。
Selector的作用相当这个人的工作,每个鸡笼相当于一个SocketChannel,单个线程通过Selector可以管理多个SocketChannel。
A Thread uses a Selector to handle 3 Channels
为了实现Selector管理多个SocketChannel,必须将多个具体的SocketChannel对象注册到Selector对象,并声明需要监听的事件,目前有4种类型的事件:
connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT(8)
accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT(16)
read:读事件,对应值为SelectionKey.OP_READ(1)
write:写事件,对应值为SelectionKey.OP_WRITE(4)
当SocketChannel有对应的事件发生时,Selector能够觉察到并进行相应的处理。
为了更好地理解NIO Socket,先来看一段服务端的示例代码
ServerSocketChannel serverChannel =ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(newInetSocketAddress(port));
Selector selector=Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);while(true){int n =selector.select();if (n == 0) continue;
Iterator ite= this.selector.selectedKeys().iterator();while(ite.hasNext()){
SelectionKey key=(SelectionKey)ite.next();if(key.isAcceptable()){
SocketChannel clntChan=((ServerSocketChannel) key.channel()).accept();
clntChan.configureBlocking(false);//将选择器注册到连接到的客户端信道,//并指定该信道key值的属性为OP_READ,//同时为该信道指定关联的附件
clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
}if(key.isReadable()){
handleRead(key);
}if (key.isWritable() &&key.isValid()){
handleWrite(key);
}if(key.isConnectable()){
System.out.println("isConnectable = true");
}
ite.remove();
}
}
服务端连接过程
1、创建ServerSocketChannel实例serverSocketChannel,并bind到指定端口。
2、创建Selector实例selector;
3、将serverSo