fengshantao 2020-04-11
我们通过一个简单的Netty代码了解到了Netty中的核心组件,这一篇我们将围绕核心组件中的Channel来展开学习。
Channel的简介
Channel代表着与网络套接字或者能够进行IO操作(read、write、connect或者bind)的组件的联系,一个Channel向用户提供了如下内容:
1、Channel当前的状态,比如是否打开、是否连接;
2、Channel的配置参数,比如接收缓冲区的大小;
3、Channel支持的IO操作(read、write、connect或者bind);
4、用于支持处理与Channel关联的所有IO事件和请求的ChannelPipeline组件。
Netty中的所有IO操作都是异步的。这意味着任何IO调用都将立即返回,而不能保证所请求的IO操作在调用结束时完成。相反,将返回一个带有ChannelFuture的实例,该实例将在请求的IO操作成功、失败或取消时通知应用。
Channel可以具有父级,具体取决于其创建方式。例如,SocketChannel在ServerSocketChannel接受它时,通过parent()方法将ServerSocketChannel作为它的父级返回。层次结构的语义取决于Channel所属的传输实现。例如,可以编写一个新的Channel实现,以创建共享一个套接字连接的子通道,就像BEEP和SSH一样。
某些传输公开了特定于该传输的其他操作,可以将Channel向下转换为子类型以调用此类操作。例如,对于旧的IO数据报传输,DatagramChannel提供了join /leave操作。
一旦使用完Channel,调用close()或close(ChannelPromise)释放资源就显得尤为重要,这样做可以确保以适当的方式(即文件句柄)释放所有资源。
Channel的方法
学习Channel提供的方法,其实可以结合上述简介部分来看。
比如,有关Channel的状态,我们可以看到这几个方法:
是否打开看isOpen方法,是否注册到EventLoop看isRegistered方法,是否连接看isActive方法。
Channel的配置参数方法可以看config方法:
该方法返回了ChannelConfig对象,这个接口定义了Channel的配置参数集合,但是在实际应用中,需结合实际的传输协议来设置具体的ChannelConfig,比如对于TCP/IP协议,需要具体被设置的对象就是SocketChannelConfig。
通过pipeline()方法,可以获取到Channel的ChannelPipeline对象,正如上文所述,ChannelPipeline也是Netty的核心组件,它可以理解为是ChannelHandler的容器,用于处理Channel的所有事件。
简介中提到了Netty异步操作会返回ChannelFuture对象,那么在Channel所提供的方法中是如何体现和这个对象的交互的呢?答案就是我们可以从closeFuture()方法中看到ChannelFuture对象,这个方法告诉我们Channel关闭时将返回用于通知的ChannelFuture,只有Channel真正的被关闭完成后,才会通过ChannelFuture回调通知到应用。
关于ChannelPipeline、ChannelHandler和ChannelFuture,我们将在后续的文章中学习。
除了上述方法,Channel还有很多方法,比如:
每一个Channel都可以有自己的id,ChannelId有2个主要的方法,asShortText()会返回短的但是全局不唯一的标识符,asLongText()会返回长的同时全局唯一的标识符。
eventLoop()会返回Channel所注册之上的EventLoop,EventLoop也是Netty的核心组件,我们也将在后续的文章中学习。
parent()会返回Channel的父级,如果没有父级则返回null。
另外Channel中还有一个内部类Unsafe,这个类的方法不建议外部使用,仅限于Netty内部使用。
源码流程
以服务端为例,在我们服务端DEMO中,可以看到代码中并未显式的创建Channel和将Channel注册到EventLoop,那么服务端启动时是如何创建Channel及将Channel注册到EventLoop呢?让我们一起来看下启动的源代码,一窥究竟。
首先,在Server中调用的是bind方法,bind里面调用的是doBind。
在doBind中,进入了启动的核心逻辑initAndRegister。
initAndRegister,顾名思义,就是初始化和注册,初始化前就有Channel的创建,注册里面包含了Channel注册到EventLoop的过程。
让我们继续看Channel是如何创建的,调用了ReflectiveChannelFactory的newChannel方法,是通过反射的方式来创建的Channel。
进入到我们的DemoServer中的NioServerSocketChannel,继续查看它的构造方法。
经过一步一步的跟进,可以在AbstractChannel看到Channel的构造过程,在这个方法中,我们可以看到熟悉的id、parent、unsafe和pipeline,这些都是Channel中的关键属性或者对象。
创建完成后,先忽略初始化,咱们再来看下Channel是如何注册的?我们来看config().group().register(channel)。
先来看下next()方法返回的是什么?
最后可以发现next()方法返回的是SingleThreadEventLoop,其实到这一步我们也可以知道Netty中的MultithreadEventLoopGroup里面可以获取到很多SingleThreadEventLoop,而SingleThreadEventLoop是一个单线程任务执行的事件循环,在它的父类SingleThreadEventExecutor中我们可以找到这个Thread。
继续看register方法。
最后会进入到真正的执行注册的AbstractUnsafe类的register0方法中。
进一步跟进,调用doRegister方法。
最终会进入到AbstractNioChannel的doRegister方法。在这里我们可以看到NIO的身影,比如SelectionKey、Selector等。到这里,我们就可以看到Channel是如何注册到NioEventLoop,它的底层本质也就是NIO中的Channel注册到Selector,因为在NioEventLoop中集成了一个Selector。
至此,我们已经知道了Channel的创建和注册的过程。
最后总结一下:
1、服务端这块,Channel创建的过程是通过反射来创建的,最终是进入到了AbstractChannel的构造函数中;
2、服务端这块,Channel注册到EventLoop的过程,本质上也就是NIO中的Channel注册到Selector的过程;
3、看源码的过程中,其实还有很多有意思的细节,比如创建Channel的同时其实ChannelPipeline也创建好了,注册的时候其实会判断注册线程和当前线程是不是一个线程来看是立即注册还是新起线程注册?这些后面再来进一步的学习和分析。