前言
本文 debug 的代码来自于
[ https://www.jianshu.com/p/209576915459 ] 一文中的
[ 2.1 LineBasedFrameDecoder + StringDecoder (基于回车换行符解决粘包拆包问题) ]
1.服务端的启动
1.1 Server启动流程
1.初始化
1.1 属性赋值
>> 初始化创建 2 个 NioEventLoopGroup:
其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。
>> 基于 ServerBootstrap(服务端启动引导类):
配置 EventLoopGroup、Channel 类型,连接参数、配置入站、出站事件 handler。
1.2 初始化
2.register
1.2 Server启动之后
Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。
NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,
每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。
#每个 boss NioEventLoop 循环执行的任务包含 3 步:
1)轮询 Accept 事件;
2)处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
3)处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。
#每个 worker NioEventLoop 循环执行的任务包含 3 步:
1)轮询 Read、Write 事件;
2)处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
3)处理任务队列中的任务,runAllTasks。
#其中任务队列中的 Task 有 3 种典型使用场景:
1) 用户程序自定义的普通任务:
ctx.channel().eventLoop().execute(newRunnable() {
@Override
publicvoidrun() {
//...
}
});
2) 非当前 Reactor 线程调用 Channel 的各种方法:
例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,
然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。
最终的 Write 会提交到任务队列中后被异步消费。
3) 用户自定义定时任务:
ctx.channel().eventLoop().schedule(newRunnable() {
@Override
publicvoidrun() {
...
}
}, 60, TimeUnit.SECONDS);
3.数据在ChannelPipeline中的具体流动过程
// Channel-ChannelPipeline-ChannelHandlerContext-ChannelHandler
当client与server建立tcp连接后, 即在二者之间创建了一个channel,
server也会在该channl上, 创建一条channelpipeline,
并在channlpipeline中添加一系列的channelhandlercontext及channelhandler.
...childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline()
// 这里结合 LineBasedFrameDecoder + StringDecoder 实现粘包拆包的处理, 其中 LineBasedFrameDecoder 是基于 \n 或 \r\n 实现
// 这里需要注意: 单条消息不能超过给定的最大限度, 否则会抛出异常
.addLast("lineBasedFrameDecoder", new LineBasedFrameDecoder(1024))
.addLast("stringDecoder", new StringDecoder())
.addLast("serverhandler01", new ServerHandler01());
}
});
#以fireChannelRead为例
当client向server端通过Channel进行数据交互时(连接建立之后),
请求数据在server端的channelpipeline中的流转过程如下:
step9之后, 然后重复 step5-step8, 直到链表的尾部.
当从server端进行出站时, 反过来, 先从链表的尾部开始, 寻找outbound对应的handler,
直至链表头部, 至此, 在服务端的handler的出入站便结束了.
客户端的出入站与之类似, 此处不再另行分析.
#这里需要注意的是:
>> 如果自定义 ChannelInboundHandler 时, 请评估是否要在对应方法里调用firexxx方法,
以便于将事件继续传递给ChannelHandlerContext的双向链表中的下一个节点, 否则, 将可能停止事件传播, 带来问题.
>> 同样的, 如果自定义 ChannelOutboundHandler时, 也请评估是否要在对应的方法里, 调用write或其他方法,
以便于将事件继续传递给ChannelHandlerContext的双向链表中的下一个节点, 否则, 将可能停止事件传播, 带来问题.
Netty中如何解决select空轮询导致cpu使用率升至100%的bug
https://blog.csdn.net/qq_41884976/article/details/91913820
参考资料
李林锋. (2015). Netty权威指南(第2版).
https://blog.csdn.net/u010739551/article/details/80519295
https://segmentfault.com/a/1190000007282789
https://segmentfault.com/a/1190000007283053