本文转载自微信公众号“是的'练级攻略”,作者是是的。转载本文请联系yes的练级指导公众号。你好,我是。在深入Netty之前,我觉得有必要对一下JavaNIO的基础知识,因为Netty对底层网络I/O的操作是基于JavaNIO的,所以了解一下是很有必要的。我们在看源码的时候,会有很多概念,比如Channel、Selector、SelectionKey、Buffer等等,在这篇文章中,我们将了解这些术语代表什么,代表什么意思。关于JavaNIO的核心,大体上包括以下三点,即:ChannelBufferSelector什么是Channel,翻译为通道。我们可以往channel中写入数据,也可以从channel中读取数据,它是双向的,它是和Buffer相匹配的,也就是说,如果要向channel中写入数据,就必须将数据写入到一个Buffer中,然后写入频道。要从通道中读取数据,必须先将通道数据读入一个Buffer,然后再进行操作。NIO中的Channel有很多种:SocketChannelServerSocketChannelDatagramChannelFileChannelSocketChannelSocket,我们可以直接使用它作为建立的连接。通过SocketChannel,我们可以使用TCP协议来读写网络数据。ServerSocketChannel可以类比为ServerSocket,即服务端创建的Socket。它的作用是监听新建立的TCP连接,为新连接创建对应的SocketChannel。之后,通过新创建的SocketChannel,就可以读写网络数据,与对端进行交互了。可见主要是用来接收新连接的。这个功能主要由服务端来完成,所以称为ServerSocketChannel。DatagramChannel看到Datagram就应该知道是UDP协议,是无连接协议。DatagramChannel可用于直接通过UDP读写网络数据。FileChannel文件通道用于读写文件的数据。我们日常的开发主要是基于TCP协议,所以可以重点关注SocketChannel和ServerSocketChannel。我们回头继续看SocketChannel和ServerSocketChannel。SocketChannel主要出现在两个地方:Client,客户端创建一个SocketChannel来连接远程服务器。在服务器端,服务器使用ServerSocketChannel接收到新的连接后,为其创建一个SocketChannel。随后,client和server就可以通过这两个SocketChannels相互发送和接收数据了。ServerSocketChannel主要出现在一个地方:服务端。服务端需要绑定一个端口,然后监听新连接的到来,这是由ServerSocketChannel完成的。服务器经常使用一个线程,一个死循环,不断接收新连接的到来。ServerSocketChannelserverSocketChannel=ServerSocketChannel.open();......while(true){//接收到新的连接和SocketChannel的区别和作用。BufferBuffer说白了就是内存中一块可以读写的地方,叫做缓冲区,用来缓存数据。其实真的没什么好说的,顶多说说JavaNIOBuffer的API。但是API太死板了,自己上网搜吧。给大家一个结论,这个API非常难用,稍有遗漏就容易出bug,还有很多优化,所以Netty没有使用JavaNIOBuffer而是实现了一个Buffer本身,称为ByteBuf。后面分析ByteBuf的时候再来看一下。现在你只需要知道Buffer主要是用来缓存通道的读写数据。对了,看到这里,可能有人会问为什么Channel一定要和Buffer一起使用呢?其实网络数据是面向字节的,只是我们读写的数据往往是多字节的。如果我们不用Buffer,那我们就得一个字节一个字节地调用读写,是不是很麻烦?所以我们建一个Buffer把数据收集在一起,这样后续的调用就可以更好的处理完整的数据,方便异步处理等等。SelectorI/O多路复用的核心。可以在选择器上注册多个通道。从上面我们知道,一个Channel对应一个connection,所以一个Selector可以管理多个Channel。具体管理什么?当任意一个Channel发生读写事件时,可以通过Selector.select()捕获事件的发生,所以我们使用一个线程无限循环调用Selector.select(),这样我们就可以使用一个线程管理多连接减少线程数,减少线程上下文切换,节省线程资源。这是Selector的核心功能,后面我们会详细介绍它是如何管理的。首先,创建一个选择器。Selectors选择器=Selector.open();然后,你需要将管理的Channel注册到Selector中,声明你感兴趣的事件。SelectionKeykey=channel.register(selector,Selectionkey.OP_READ);上面有四种类型的事件。注册时,可以同时对多种类型的事件感兴趣,例如:SelectionKeykey=channel.register(selector,Selectionkey.OP_READ|SelectionKey.OP_WRITE);这样,当这个Channel发生读写事件时,我们调用Selector.select()就可以知道事件发生了。具体来说,Selector.select()有3个重载方法:intselectNow(),不管有没有事件,立即返回intselect(超时时间长),最多阻塞超时时间(或被唤醒),如果事件提前发生,则early返回intselect(),它会一直阻塞直到事件发生(或被唤醒)。返回值是就绪通道数。一般判断为大于0即可进行后续操作。后续操作是调用:SetselectedKeys=selector.selectedKeys();获取一个Set类型的selectedKeys集合,那么这个selectedKeys是什么呢?我们来看看它的方法和成员:看到这些成员,其实我们就会很清楚了,我们可以通过selectedKey知道当前发生了什么事件,包括isAcceptable,isReadable等等。然后还可以获取相应的通道进行相应的读写操作,以及获取附件等。所以在拿到selectedKeys之后,就可以使用迭代器遍历所有发生事件的连接,然后进行操作。大致使用的代码如下:while(true){intreadyNum=selector.select();if(readyNum==0){continue;}SetselectedKeys=selector.selectedKeys();IteratorkeyIterator=selectedKeys.iterator();while(keyIterator.hasNext()){SelectionKeykey=keyIterator.next();if(key.isAcceptable()){//一个连接被ServerSocketChannel接受。}elseif(key.isConnectable()){//一个连接被远程服务器建立。}elseif(key.isReadable()){//achannelisreadyforreading}elseif(key.isWritable()){//achannelisreadyforwriting}keyIterator.remove();//执行完后需要在循环中移除自己}}还有一个方法是Selector.wakeup(),可以唤醒被阻塞的Selector。对了,还有一点没说,就是Channel如果要和Selector匹配,必须是非阻塞的,也就是configurechannel.configureBlocking(false);从上面的操作我们可以知道,Selector在处理事件的时候一定要快。如果一个事件处理时间过长,Selector上注册到其他连接的事件将得不到及时处理,导致客户端阻塞。至此,你应该清楚Selector是如何管理这么多连接的了。参考:https://ifeve.com/java-nio-all/
