所有的channel
通过regiest
交给selector
进行管理。
但是所有的selector
并不是直接对channel
进行管理,而是通过创建selectionKey
对channel
进行管理。
也就是这样
同时,注册时还需要声明类型
,也就是说生成的selectionKey
中还有类型的划分,也就是如此
我们regiest
注册之后,创建的selectionKey
可以分作三种状态
listen
:注册监听
trigger
:监听触发
cancel
:取消监听
也就是说,selector
下管理的selectionKey
分作了三个集合,然后之下是具体的selectionKey
。
其中三种集合的状态关系如下
listen
:regiest
自动添加,cancel
操作后移除,添加至cancel
队列。
trigger
:当事件触发之后,系统自动从listen
添加到trigger
,不可手动添加。
cancel
:cancel
取消关注之后从trigger
移动到cancel
。
自动变换
listen
到trigger
的变换,是系统自动通知变换的。这种事件通知是底层自动的,我们只需要处理
通知
的元素即可。手动变换
- 当事件不需要关注的时候,需要手动去
cancal
。trigger
事件处理完毕之后,需要进行remove
,否则事件会重复触发,甚至报错。
type | point | description |
---|---|---|
OP_READ | server client | 读取事件 |
OP_WRITE | server client | 写入事件 |
OP_CONNECT | client | 连接事件 客户端独有 |
OP_ACCEPT | server | 接收事件 服务端独有 |
select
:一直阻塞,直到触发
selectNow
:不阻塞,直接执行
select(timeout)
:阻塞timeout
一般常用select
。
public class NioMain {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("localhost", 8989));
Selector selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
selectedKeys.forEach(selectedKey->{
try{
if(selectedKey.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) selectedKey.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectedKey.isReadable()){
SocketChannel client = (SocketChannel) selectedKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
client.configureBlocking(false);
client.read(byteBuffer);
byteBuffer.flip();
client.write(byteBuffer);
}
}catch (Exception e){
e.printStackTrace();
}
});
selectedKeys.clear();
}
}
}
异步配置
serverSocketChannel.configureBlocking(false);
使用
selector
需要异步模式,模式不匹配会报错。通道注册
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
通道主动注册,传入
seletor
和eventType
。阻塞
selector.select();
直到
eventType
触发,然后会自动把触发的selectionKey
添加到trigger
集合。通过处理
trigger
集合完成目的。集合获取
Set<SelectionKey> selectedKeys = selector.selectedKeys();
代码中使用的是
clear
对事件后续进行清理。如果使用
iterator
进行处理,记得remove
。
trigger
事件队列不会自动清理,处理完毕的事件需要手动移除,否则会重复触发,甚至报错。类型判断
selectedKey.isAcceptable()
function type isAcceptable
OP_ACCEPT
isReadable
OP_READ
isConnectable
OP_CONNECT
isWritable
OP_WRITE
通过事件选择性处理。
通道获取
SocketChannel client = (SocketChannel) selectedKey.channel();
通过
selectedKey
进行通道获取,然后进行读取或写入操作。再次监听
socketChannel.register(selector, SelectionKey.OP_READ);
一般情况下,我们很少不再关注
cancel
,但是很多时候会新增关注regiest
。在
while
中,我们可以动态的增加关注的channel
,后续增加的关注通道,仍然采取同样的管理办法。不过值得注意的是。获取
channel
时需要判断类型,否则会强转失败。
public class BioMain {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8989));
while(true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
int readLength = -1;
byte[] bs = new byte[1024];
while((readLength = inputStream.read(bs))!=-1){
outputStream.write(bs, 0 , readLength);
}
inputStream.close();
outputStream.close();
}
}
}
上面的是BIO
的代码结构,看起来好像更简便。
和NIO
的多个
相比较,好像BIO
比较单一来着,这算是直观感受和细节屏蔽给我们的错误视觉吧。
最直白的,就是连接的个数了。
BIO
:看似采用同一套逻辑,但是瞒着我们开了好多线程
NIO
:看起来一大堆东西,过于繁杂,但是除了事件通知
,仅此而已。
我们在NiO
中挨个数channel
,这不是更能说明多个连接使用的是同一个thread
么。
BIO
的理解每个连接一条线程,过于浪费资源。
各种事件之间纯粹阻塞,不够及时,浪费资源,响应也慢。
但是各自占用一条线程,处理能力强。
总结
除去底层事件通知的短板,在高并发链接上弱势,在多内容传输上有一定优势。
适合少链接,大量处理场合。
NIO
的理解多链接共用单线程,节省线程开销,但是限制处理能力。
事件通知提高性能,快速感知状态变化。
总结
适合高并发链接,能够处理多变状态,快速反应和处理。
但是单线程限制处理能力,当处理复杂内容时,单个耗时在串行处理过程中阻塞明显,降低效率。
Buffer
对数据进行包装
Channel
对数据进行传输
而selector
就是对channel
进行管理
通过对channel
状态切换的管理,连接上处理操作。
让IO
操作不再是一昧的死等
,让每一步都更有意义
。
code here