在阅读Netty的源码之前,首先先来简单了解一下3个核心类的作用。
EventLoop
从EventLoop的继承图中可以看出,EventLoop继承了jdk的Executor和ScheduleExecutorService等接口,熟悉线程池的朋友应该知道这两个接口主要作用是用来提交任务和执行定时任务的。
而EventLoop的主要作用有两个,一方面在内部维护了一个任务队列,另一方面EventLoop持有一个多路复用选择器Selector。EventLoop会不停的从任务队列中获取任务执行,同时也会关注Selector是否有可用的连接。如果有可用的连接,EventLoop也会处理这些连接。
protected void run() {
int selectCnt = 0;
for (; ; ) {
if (ioRatio == 100) {
try {
if (strategy > 0) {
//查看Selector的连接状态
processSelectedKeys();
}
} finally {
//运行任务队列的任务
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
}
}
上述代码是EventLoop的一个实现类NioEventLoop的run方法。为了阅读方便去掉了其他一些代码。从上述代码中可以看出,EventLoop在一个死循环中不停的关注任务队列和Selector的连接情况。
EventLoopGroup
EventLoopGroup从名字也可以看出,这是一个EventLoop的集合。这个类的内部持有多个EventLoop,他的主要作用就是将收到的任务均匀的分配给内部持有的EventLoop。
Channel
Channel我们可以把他理解为Netty对jdk的Socket的封装,一个Channel内部会持有一个Socket。同时Channel内部还会持有一个ChannelPipeline对象。ChannelPipeline对象会把我们对Socket的处理逻辑串联起来。
I/O Request
via {Channel} or
{ChannelHandlerContext}
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
|
+-------------------------------------------------------------------+
这是ChannelPipeline的注释,我觉得已经足够形象了。
所以Channel对象的作用,就是把我们对流的处理逻辑串联起来,然后让接收的Socket按照串联起来的逻辑运行。
在了解了这三个关键类的作用之后,在结合上一篇的服务端代码,我们可以尝试着勾画一下Netty框架的处理流程。
1、 首先有一个单独EventLoop(boss)会负责网络连接的接收,在接收到网络连接之后会把该连接传递给EventLoopGroup(worker)。
2、 然后该EventLoopGroup(worker)会均匀的分配网络连接给内部持有的EventLoop去处理。
3、 EventLoop在获取了连接之后会不停的关注连接的读写事件,如果有准备完毕的读写事件,则按照Channel内部的Pipline进行链式处理。
下面我们就要从源码出发,看看Netty内部的处理流程到底是怎样的。