由于Netty优秀的设计和封装,开发一个高性能网络程序就变得非常简单,本文从一个简单的服务端落地简单介绍一下Netty中的几个核心组件,希望对你有帮助。
快速落地一个服务端
我们希望通过Netty快速落地一个简单的主从reactor模型,由主reactor对应的线程组接收连接交由acceptor创建连接,与之建立的客户端的读写事件都会交由从reactor对应的线程池处理:
基于此设计,我们通过Netty写下如下代码,可以看到我们做了如下几件事:
- 声明一个服务端创建引导类ServerBootstrap ,负责配置服务端及其启动工作。
- 声明主从reactor线程组,其中boss可以看作监听端口接收新连接的线程组,而work则是负责处理客户端数据读写的线程组。
- 基于上述线程池作为group的入参完成主从reactor模型创建。
- 通过channel函数指定server channe为NioServerSocketChannel即采用NIO模型,而NioServerSocketChannel我们可以直接理解为serverSocket的抽象表示。
- 通过childHandler方法给引导设置每一个连接数据读写的处理器handler。
最后调用bind启动服务端并通过addListener对连接结果进行异步监听:
对于客户端的发送的数据,我们都会通过ChannelInboundHandlerAdapter添加顺序处理,就如代码所示我们的执行顺序为InboundHandlerA->InboundHandlerB->ServerHandler,对此我们给出InboundHandlerA的代码,InboundHandlerB内容一样就不展示了:
而ServerHandler的则是:
- 客户端与服务端建立连接,对应客户端channel被激活,触发channelActive方法。
- ChannelHandlerContext 的 Channel 已注册到其 EventLoop 中,执行channelRegistered。
- 将 ChannelHandler 添加到实际上下文并准备好处理事件后调用。
解析客户端的数据然后回复Hello Netty client :
我们通过telnet 对应ip和端口后发现服务端输出如下内容,也很我们上文说明的一致:
然后我们发送消息 1,可以看到触发所有inbound的channelRead方法:
然后我们回复hello netty client,按照添加的倒叙触发OutBoundHandler:
详解Netty中的核心组件
channel接口
channel是Netty对于底层class socket中的bind、connect、read、write等原语的封装,简化了我们网络编程的复杂度,同时Netty也提供的各种现成的channel,我们可以根据个人需要自行使用。 下面便是笔者比较常用的Tcp或者UDP中比较常用的几种channel。
- NioServerSocketChannel:基于NIO选择器处理新连接。
- EpollServerSocketChannel:使用 linux EPOLL Edge 触发模式实现最大性能的实现。
- NioDatagramChannel:发送和接收 AddressedEnvelope 的 NIO 数据报通道。
- EpollDatagramChannel:使用 linux EPOLL Edge 触发模式实现最大性能的 DatagramChannel 实现。
EventLoop接口
在Netty中,所有channel都会注册到某个eventLoop上, 每一个EventLoopGroup中有一个或者多个EventLoop,而每一个EventLoop都绑定一个线程,负责处理一个或者多个channel的事件:
这里我们也简单的给出NioEventLoop中的run方法,它继承自SingleThreadEventExecutor,我们可以大概看到NioEventLoop的核心逻辑本质就是轮询所有注册到NioEventLoop上的channel(socket的抽象)是否有其就绪的事件,然后
pipeline和channelHandler以channelHandlerContext
每一个channel的事件都会交由channelHandler处理,而负责同一个channel的channelHandler都会交由pipeline一条逻辑链进行连接,这两者的的关系都会一一封装成channelHandlerContext,channelHandlerContext主要是负责当前channelHandler和与其同一条channelpipeline上的其他channelHandler之间的交互。
举个例子,当我们收到客户端的写入数据时,这些数据就会交由pipeline上的channelHandler处理,如下图所示,从第一个channelHandler处理完成之后,每个channelHandlerContext就会将消息转交到当前pipeline的下一个channelHandler处理:
假设我们的channelHandler执行完ChannelActive后,如希望继续传播则会调用fireChannelActive:
查看其内部逻辑即可知晓,它就是通过AbstractChannelHandlerContext得到pipeline的下一个ChannelHandler并执行其channelActive方法:
回调的思想
我们可以说回调其实是一种设计思想,Netty对于连接或者读写操作都是异步非阻塞的,所以我们希望在连接被建立进行一些响应的处理,那么Netty就会在连接建立的时候方法暴露一个回调方法供用户实现个性逻辑。
例如我们的channel连接被建立时,其底层就会调用invokeChannelActive获取我们绑定的ChannelInboundHandler并执行其channelActive方法:
于是就会调用到我们服务端ServerHandler 的channelActive方法:
Future异步监听
为保证网络服务器执行的效率,Netty大部分网络IO操作都采用异步的,以笔者建立连接设置的监听器为例,当前连接成功后,就会返回给监听器一个java.util.concurrent.Future,我们就可以通过这个f获取连接的结果是否成功:
我们步入DefaultPromise的addListener即可发现其内部就是添加监听后判断这个连接的异步任务Future是否完成,如果完成调用notifyListeners回调我们的监听器的逻辑: