问题出在笔者在学习NIO的Selector使用时,由于不了解Selector的机制,导致程序出现空指针异常。问题来自以下两段代码。问题现场还原服务器代码包com.jielihaofeng.netty.c4;importlombok.extern.slf4j.Slf4j;importjava.io.IOException;importjava.net.InetSocketAddress;importjava.nio.ByteBuffer;importjava.nio。channels.SelectionKey;importjava.nio.channels.Selector;importjava.nio.channels.ServerSocketChannel;importjava.nio.channels.SocketChannel;importjava.util.Iterator;/***@descriptionSelector使用*@authorJohnnieWind*@date2021/10/1122:08*/@Slf4jpublicclassServerSelector{publicstaticvoidmain(String[]args)throwsIOException{//1.创建一个选择器来管理多个通道Selectorselector=Selector.open();ServerSocketChannelssc=ServerSocketChannel.open();ssc.configureBlocking(假);//必须配置,否则会报java.nio.channels.IllegalBlockingModeException//2.建立selector和channel的连接//SelectionKey是以后某个事件发生后,可以通过它来知道该事件是哪个通道事件SelectionKeysscKey=ssc.register(selector,0,null);//四种类型的事件://accept-有连接请求时会触发//connect-是客户端,连接建立后触发//read-可读事件//write-可写事件//key只关注accept事件sscKey.interestOps(SelectionKey.OP_ACCEPT);log.debug("sscKey:{}",sscKey);ssc.bind(新InetSocketAddress(8080));while(true){//3.select方法,如果没有事件发生,线程会阻塞,如果有事件,线程会恢复运行//select在事件没有处理的时候不会阻塞,而且它必须在事件发生后处理或取消,并且不能忽略selector.select();//4.处理事件,selectedKeys包含所有发生的事件Iteratoriterator=selector.selectedKeys().iterator();//接受,读取while(iterator.hasNext()){SelectionKeykey=iterator.next();log.debug("key:{}",key);//5.区分事件类型if(key.isAcceptable()){//如果接受ServerSocketChannelchannel=(ServerSocketChannel)key.channel();SocketChannelsc=channel.accept();sc.configureBlocking(false);SelectionKeyscKey=sc.register(selector,0,null);scKey.interestOps(SelectionKey.OP_READ);log.debug("sc{}",sc);log.debug("scKey:{}",scKey);}elseif(key.isReadable()){//如果读取SocketChannelchannel=(SocketChannel)key.channel();//获取触发事件的通道ByteBufferbuffer=ByteBuffer.allocate(16);通道.read(缓冲区);缓冲区.翻转();while(buffer.hasRemaining()){System.out.println((char)buffer.get());}buffer.clear();}}}}}客户端代码包com.jielihaofeng.netty.c4;importjava.io.IOException;importjava.net.InetSocketAddress;importjava.nio.channels.SocketChannel;/***@descriptionClient*@author约翰尼·温德*@date2021/10/1122:17*/publicclassClient{publicstaticvoidmain(String[]args)throwsIOException{SocketChannelsc=SocketChannel.open();sc.connect(newInetSocketAddress("localhost",8080));System.out.println("等待...");//注意这里需要打断点才能开始调试}}启动调试过程Debug或Run模式运行服务器代码Debug模式运行客户端代码。启动成功,ServerSelector控制台输出如下图所示:接下来,切换到客户端的调试模式窗口,按Alt+F8,或者点击Evalute图标,打开评估器,切换到代码模式:输入以下代码写入socketChannel“hi”:sc.write(Charset.defaultCharset().encode("hi"));点击Evalute进行评估,然后切换ServerSelector的调试窗口,发现输出空指针异常:21:11:44.400[main]DEBUGcom.jielihaofeng.netty.c4.ServerSelector-sscKey:sun.nio。ch.SelectionKeyImpl@c46bcd421:12:08.322[main]DEBUGcom.jielihaofeng.netty.c4.ServerSelector-key:sun.nio.ch.SelectionKeyImpl@c46bcd421:12:08.323[main]DEBUGcom.jielihaofeng.netty.c4。ServerSelector-scjava.nio.channels.SocketChannel[connectedlocal=/127.0.0.1:8080remote=/127.0.0.1:62001]21:12:08.323[main]DEBUGcom.jielihaofeng.netty.c4.ServerSelector-scKey:sun.nio.ch.SelectionKeyImpl@4923ab2421:23:57.723[main]DEBUGcom.jielihaofeng.netty.c4.ServerSelector-key:sun.nio.ch.SelectionKeyImpl@c46bcd4Exceptioninthread"main"java.lang.NullPointerExceptionatcom.jielihaofeng.netty.c4.ServerSelector.main(ServerSelector.java:57)断开连接targetVM,address:'127.0.0.1:64394',transport:'socket'对应代码行为sc.configureBlocking(false);,如下图:问题分析问题其实很简单,关键是理解Selector的设计Selector有两个集合,分别是keys和selectedKeys,keys:选择器上channel上注册的所有selectionKeys。selectedKeys:所有注册在selector上,等待IO操作发生(即事件发生)的通道的selectionKey。我把程序执行过程大致分为四个时间点:服务端注册时,客户端启动时,客户端注册时,客户端写消息时。通过分析相应时间点的代码,得到如下状态图:service注:selector会在客户端启动时有事件发生后,将key添加到selectedKeys中。当事件被处理时,selectionKey将清除事件,但不会删除它。所以在接下来的过程中(客户端注册时),我们看到sscKey的事件标志被清除了,从“sscKey@c46bcd4-acceptevent-ssc”变成了“sscKey@c46bcd4-ssc”。客户端注册时,客户端写入消息,然后继续遍历,Iteratoriterator=selector.selectedKeys().iterator();发现selectedKeys集合中有两个元素:第一个是监听accept事件的server端ssc剩余的key和后续clientsc监听read事件的新增key!迭代器得到第一个元素,进入可接受的if分支:if(key.isAcceptable()){//如果是accept//...}而此时没有新的client加入,导致获取到的sc为空!SocketChannelsc=channel.accept();//此时的事件是sc的读取,ssc拿到后sc为空!这导致此行中的空指针:sc.configureBlocking(false);因此,必须在处理完事件后移除selectedKeys集合中的元素。SelectionKeykey=iterator.next();//处理完事件后,一定要从selectedKeys集合中删除iterator.remove();Review&Summary本次事件回顾1.客户端连接时触发sscKey的accept事件,无移除事件。2、客户端写消息时,触发scKey上的read事件,获取最后一个ssckey的accept事件进行处理,没有客户端连接进入错误的事件分支,导致获取客户端的通道被空,然后是一个空指针异常总结selector在select事件发生后会将事件相关的key放入selectedKeys集合中,事件处理完后不会主动从selectedKeys集合中删除,所以需要删除通过它自己。即在遍历selectedKeys集合时,使用迭代器进行遍历,使用Iterator的remove()方法删除元素。