java学习历程:NIO为什么SelectionKey在被轮询后需要remove()
学习NIO的过程中,对selector选择器的知识产生了兴趣,尤其是关于SelectionKey的轮询后remove()的问题,博主尝试简单地解释一下NIO如何实现非阻塞的。
首先是客户端的代码:
public void testNonBlockingNIOClient() throws IOException{
//客户端
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9899));
//2.切换成非阻塞模式
sChannel.configureBlocking(false);//key
//3.缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4.读文件发送至服务端
buf.put(new Date().toString().getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
sChannel.close();
}
再者是服务端代码:
public void testNonBlockingNIOServer() throws IOException{
//服务端
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换成非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9899));
//4.获取选择器selector
Selector selector = Selector.open();
//5.将通道注册到选择器上
ssChannel.register(selector, SelectionKey.OP_ACCEPT);//ops选择键,监控通道什么状态
//return SelectionKey
//1 读 OP_READ
//4 写 OP_WRITE
//8 连接 OP_CONNECT
//16接受 OP_ACCEPT
//|连接符监控多个
//6.轮询式地获取选择器上准备就绪的事件
while(selector.select()>0){
//7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
//8.获取准备就绪的事件
SelectionKey sk = it.next();
//9.判断具体是什么事件准备就绪
if(sk.isAcceptable()){
//若“接收就绪”
SocketChannel SChannel = ssChannel.accept();
//切换非阻塞模式
SChannel.configureBlocking(false);
SChannel.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()){
//若读就绪
SocketChannel SChannel = (SocketChannel) sk.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while((len = SChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//10.重点,取消选择键
it.remove();
}
}
}
可以发现服务端的最后进行了remove()操作,将SelectionKey从迭代器中删除了,博主一开始总觉得很纳闷,SelectionKey中可是记录了相关的channel信息,如果将SelectionKey删除了,那不就代表着将通道信息也抹除了吗,那后续还怎么继续获取通道,说来惭愧,这问题问的确实缺乏水准。
后来博主理了理selector的思路,要知道,一码事归一码事,channel是注册在selector中的,在后面的轮询中,是先将已准备好的channel挑选出来,即selector.select(),再通过selectedKeys()生成的一个SelectionKey迭代器进行轮询的,一次轮询会将这个迭代器中的每个SelectionKey都遍历一遍,每次访问后都remove()相应的SelectionKey,但是移除了selectedKeys中的SelectionKey不代表移除了selector中的channel信息(这点很重要),注册过的channel信息会以SelectionKey的形式存储在selector.keys()中,也就是说每次select()后的selectedKeys迭代器中是不能还有成员的,但keys()中的成员是不会被删除的(以此来记录channel信息)。
那么为什么要删除呢,要知道,迭代器如果只需要访问的话,直接访问就好了,完全没必要remove()其中的元素啊,查询了相关资料,一致的回答是为了防止重复处理(大雾),后来又有信息说明:每次循环调用remove()是因为selector不会自己从已选择集合中移除selectionKey实例,必须在处理完通道时自己移除,这样,在下次select时,会将这个就绪通道添加到已选择通道集合中,其实到这里就已经可以理解了,selector不会自己删除selectedKeys()集合中的selectionKey,那么如果不人工remove(),将导致下次select()的时候selectedKeys()中仍有上次轮询留下来的信息,这样必然会出现错误,假设这次轮询时该通道并没有准备好,却又由于上次轮询未被remove()的原因被认为已经准备好了,这样能不出错吗?
即selector.select()会将准备好的channel以SelectionKey的形式放置于selector的selectedKeys()*使用者迭代,使用的过程中需将selectedKeys清空,这样下次selector.select()时就不会出现错误了。
附上一个非常好的连接:https://blog.csdn.net/qq_32331073/article/details/81132937
上一篇: 网络(socket)流与文件流的可用字节数available用法
下一篇: socket