Netty版本4.1.22
pipeline可以看作是一个拦截流经Channel的入站和出站事件的ChannelHandler实例链。
前面分析的NioServerSocketChannel
与NioSocketChannel
在创建时都会创建自己的pipeline,在AbstractChannel
中。
《Netty权威指南》里的一张图:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
来看看DefaultChannelPipeline
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
保存chnnel引用,创建了两个AbstractChannelHandlerContext
对象,关于head
与tail
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
final class TailContext extends AbstractChannelHandlerContext implements
ChannelInboundHandler {
head既是inbound
也是outbound
,tail是inbound
此时整个pipeline是这样。
pipeline中的每个节点是一个ChannelHandlerContext
对象。ChannelHandlerContext
代表ChannelHandler和channelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。其主要功能是管理它所关联的ChannelHandler与同在一个ChannelPipeline中的其它ChannelHandler之间的交互。
pipeline添加节点
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("IdleStateHandler", new IdleStateHandler(10, 0, 0))
.addLast("LengthFieldPrepender", new LengthFieldPrepender(4, 0))
.addLast("RpcEncoder", new RpcEncoder())
.addLast("LengthFieldBasedFrameDecoder",
new LengthFieldBasedFrameDecoder(1024 * 1024, 0,4,0,4))
.addLast("RpcDecoder", new RpcDecoder())
.addLast("RpcServerHandler", new RpcServerHandler(handlerMap));
}
})
来跟踪addLast方法
DefaultChannelPipeline:
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler); // 检测handler是否已添加
// 创建节点
newCtx = newContext(group, filterName(name, handler), handler);
// 添加节点
addLast0(newCtx);
// registered 为false表明channel并没有注册到它的pipeline上,
// 那么就将newCtx封装成一个任务加到任务链表,当channel注册完成后
// 再触发callHandlerAdded0,他会触发handler的handlerAdded
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
// 若你调用addLast时传入group,则上面newContext方法在构建headlerContext
// 的时候,会从group种next一个NioEventLoop赋给
// AbstractChannelHandlerContext.executor字段,
// 作为一个child executor,即子线程
// 有什么用处?比如这里,若存在child executor则将callHandlerAdded0交给
// 该子线程处理。若不存在,则由当前NioEventLoop的线程来处理,
// 那么当前线程是哪个线程?
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
对pipeline中context节点操作由synchronized保护,即对handler链的更改要确保安全性。
newCtx.executor()
定位到AbstractChannelHandlerContext
public EventExecutor executor() {
if (executor == null) {
return channel().eventLoop();
} else {
return executor;
}
}
public Channel channel() {
return pipeline.channel();
}
这里addLast
时没传入group,则AbstractChannelHandlerContext.executor
为null,返回与pipeline关联的channnel的EventLoop
,比如顺着上面的例子(childHandler的addLast)则这里返回的就是NioSocketChannel
初始化时创建的NioEventLoop
,执行这些代码的线程也是该eventLoop的线程,之前连接一篇中分析过,NioServerSocketChannel的线程从连接事件到来一路执行到其pipeline里ServerBootstrapAcceptor
的channelRead,再该方法里childGroup.register(child)
,跟踪注册逻辑到AbstractUnsafe的register,在这里创建启动NioSocketChannel所属NioEventLoop的线程(前提是该NioEventLoop没有线程),register0就交给它来执行,为什么说执行上面addLast的是该线程?因为ChannelInitializer的initChannel在register0里被调用,详细分析在连接一篇中。
插一句,源码分析到这里,究竟是哪个线程在执行,理清它有助于对过程的理解,总之一个NioEventLoop只有一个线程,而channel一旦被注册到某个NioEventLoop上就不会更改,对于每个channel都是单线程操作,落在代码层面要做的就是在一些要保护的方法执行前判断eventLoop.inEventLoop()
,true就直接执行,false就封装成任务放入队列,留线程启动后再执行。那么哪些方法要保护?线程又在何处启动?由之前的分析来看无论是NioServerSocketChannel还是NioSocketChannel,它们所属的NioEventLoop的线程都是在register方法里被启动的,因为register0方法是需要保护的方法。
1,checkMultiplicity检测该handler是否已添加
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
如果该handler是非共享的且已经添加过(hanler的added
标识一个handler是否已添加)则抛异常。否则h.added = true;
标识该handler为已添加。
关于共享handler
@Sharable
public class BusinessHandler {
}
isSharable() 检测的就是该注解
public boolean isSharable() {
Class<?> clazz = getClass();
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = cache.get(clazz);
if (sharable == null) {
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
2,newContext创建节点
newCtx = newContext(group, filterName(name, handler), handler);
filterName给handler创建一个唯一性的名字。
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
先来看看childExecutor(group),group为null则返回null
private EventExecutor childExecutor(EventExecutorGroup group) {
if (group == null) {
return null;
}
// 检查SINGLE_EVENTEXECUTOR_PER_GROUP属性,默认为true,代表
// 整个pipeline由一个线程来执行
Boolean pinEventExecutor = channel.config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
if (pinEventExecutor != null && !pinEventExecutor) {
return group.next(); // 若为false,则从group中选择一个线程来执行,不推荐
}
// 下面的操作就是从group中选择一个NioEventLoop,再将group与该eventLoop关联并存储
// 下次同一group用同一eventLoop
Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
if (childExecutors == null) {
// Use size of 4 as most people only use one extra EventExecutor.
childExecutors = this.childExecutors = new IdentityHashMap<EventExecutorGroup, EventExecutor>(4);
}
// Pin one of the child executors once and remember it so that the same child executor
// is used to fire events for the same channel.
EventExecutor childExecutor = childExecutors.get(group);
if (childExecutor == null) {
childExecutor = group.next();
childExecutors.put(group, childExecutor);
}
return childExecutor;
}
SINGLE_EVENTEXECUTOR_PER_GROUP
Netty参数,单线程执行ChannelPipeline中的事件,默认值为True。该值控制执行ChannelPipeline中执行ChannelHandler的线程。如果为True,整个pipeline由一个线程执行,这样不需要进行线程切换以及线程同步,是Netty4的推荐做法;如果为False,ChannelHandler中的处理过程会由Group中的不同线程执行。
继续,DefaultChannelHandlerContext
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
保存handler引用
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
3,addLast0(newCtx)添加节点
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
典型的双向链表插入过程,插入到tail
节点之前。
4, callHandlerAdded0回掉handler的handlerAdded()
之前连接一篇很长一大篇来分析ChannelInitializer
的initChannel
什么时候被调用,就是在这里 callHandlerAdded0
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
// We must call setAddComplete before calling handlerAdded.
// Otherwise if the handlerAdded method generates
// any pipeline events ctx.handler() will miss them because
// the state will not allow it.
ctx.setAddComplete();
ctx.handler().handlerAdded(ctx);
先调用setAddComplete
/**
* ChannelHandler#handlerAdded(ChannelHandlerContext) is about to be called.
*/
private static final int ADD_PENDING = 1;
/**
* ChannelHandler#handlerAdded(ChannelHandlerContext) was called.
*/
private static final int ADD_COMPLETE = 2;
/**
* ChannelHandler#handlerRemoved(ChannelHandlerContext) was called.
*/
private static final int REMOVE_COMPLETE = 3;
/**
* Neither ChannelHandler#handlerAdded(ChannelHandlerContext)
* nor ChannelHandler#handlerRemoved(ChannelHandlerContext) was called.
*/
private static final int INIT = 0;
private volatile int handlerState = INIT;
private static final AtomicIntegerFieldUpdater<AbstractChannelHandlerContext>
HANDLER_STATE_UPDATER =AtomicIntegerFieldUpdater.newUpdater
(AbstractChannelHandlerContext.class, "handlerState");
final void setAddComplete() {
for (;;) {
int oldState = handlerState;
// Ensure we never update when the handlerState is
// REMOVE_COMPLETE already.
// oldState is usually ADD_PENDING but can also be
// REMOVE_COMPLETE when an EventExecutor is used that is not
// exposing ordering guarantees.
if (oldState == REMOVE_COMPLETE ||
HANDLER_STATE_UPDATER.compareAndSet(this, oldState, ADD_COMPLETE)) {
return;
}
}
}
通常节点状态为ADD_PENDING
,CAS更改为ADD_COMPLETE
,如果是REMOVE_COMPLETE
则不应该更改它。
回到上面,最后会ctx.handler().handlerAdded(ctx);
回调该handler的handlerAdded()
方法。以ChannelInitializer
为例
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}
注释中说运行到里ctx.channel().isRegistered()
一定为true,即保证channel已注册,指的是AbstractChannel.registered
为true
,它只在regoster0
里被改为true,如何保证callHandlerAdded0
的调用满足这一条件,callHandlerAdded0在DefaultChannelPipeline的addFirst,addLast,addBefore,addAfter里被调用,在调用前会检测DefaultChannelPipeline.registered
变量值,为false则将该callHandlerAdded0
的调用延迟(延迟方法看连接一篇),该变量只在callHandlerAddedForAllHandlers
被更改,channel注册在register0里会调用pipeline.invokeHandlerAddedIfNeeded();
,如果第一次注册调用callHandlerAddedForAllHandlers,更改DefaultChannelPipeline.registered,调用callHandlerAdded0,这样保证了ctx.channel().isRegistered()
一定返回true。
回到ChannelInitializer
,它的handlerAdded
调用了initChannel
。
对之前分析中出现的pipeline操作进行分析,
- register0中出现的invokeHandlerAddedIfNeeded,fireChannelRegistered,fireChannelActive
- 连接阶段出现的fireChannelRead,fireChannelReadComplete
invokeHandlerAddedIfNeeded
在register0
中
invokeHandlerAddedIfNeeded:对pipeline中链表的添加删除等改动操作,最后都会调用callHandlerRemoved0,callHandlerAdded0,去改变节点handlerContext的状态,并调用该handler的handlerRemoved,handlerAdded。不过上述有个前提是该channel已注册,所以在未注册情况下,这两个方法的调用被封装起来,等待注册后执行,invokeHandlerAddedIfNeeded就是在注册后触发它们的方法。
fireChannelRegistered
选取一个点分析,服务启动阶段register0,invokeHandlerAddedIfNeeded执行后,NioServerSocketChannel的pipeline变为head->ServerBootstrapAcceptor->tail,假设用户没有设定bootstrap.handler(),所以就只有三个handler。
DefaultChannelPipeline:
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
传head头节点
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
既然是register0方法,那么这里(executor.inEventLoop()
返回true
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
private boolean invokeHandler() {
// Store in local variable to reduce volatile reads.
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
该方法检测ChannelHandler#handlerAdded(ChannelHandlerContext)
是否已被调用,是返回true。为什么要检测?为了确保加入到pipeline的handler的handlerAdded的执行。
回到invokeChannelRegistered,handler()返回head,来看它的channelRegistered(head),
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
再次调用了invokeHandlerAddedIfNeeded()
,确保handlerAdded的执行,ctx指的是head,ctx.fireChannelRegistered();
将channelRegistered事件传递下去
AbstractChannelHandlerContext:
public ChannelHandlerContext fireChannelRegistered() {
invokeChannelRegistered(findContextInbound());
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
当head的channelRegistered执行完,找寻handler链上下一个inbound,调用其channelRegistered
,本例子中ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter
,ServerBootstrapAcceptor 就是下一个inbound。来看看它的实现,在其父类ChannelInboundHandlerAdapter
中
ChannelInboundHandlerAdapter:
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
不做任何反应,直接传递下去,下一个inbound是tail,TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { }
作为尾节点对channelRegistered事件不做任何处理,事件到此结束。
fireChannelActive
对于服务端启动阶段,fireChannelActive并不会在注册阶段register0方法中被调用,因为NioServerSocketChannel.isActive
(该方法有多种实现,由于选的是服务端启动,这里定位到NioServerSocketChannel)只有在成功绑定地址后才会返回true。在服务端启动的绑定阶段,AbstractUnsafe的bind方法里,当doBind实际绑定操作结束后
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
invokeLater就是将其封装策成Runnable加入到taskQueue
中。这里是哪个线程在执行,服务端启动的主线程,还是在register
方法中启动的reactor线程?回到AbstractBootstrap.doBind0
,发现其将之后绑定操作交给了reactor线程来执行。这里的reactor线程指的是NioServerSocketChannel所属的NioEventLoop的线程
所以这里我们分析的阶段定为服务端启动的绑定阶段,AbstractUnsafe的bind方法里,当doBind实际绑定操作结束后,上述代码的执行
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
AbstractChannelHandlerContext
AbstractChannelHandlerContext:
private void invokeChannelActive() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelActive();
}
}
这里handler()指的是head。
HeadContext的channelActive实现
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive(); // 传递channelActive事件
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) { // true
channel.read();
}
}
HeadContext的处理是首先将channelActive事件传递给下一个inbound,随后调用readIfIsAutoRead
:channel.config().isAutoRead()
为true,跟踪read
AbstractChannel
public Channel read() {
pipeline.read();
return this;
}
DefaultChannelPipeline
public final ChannelPipeline read() {
tail.read();
return this;
}
调用了tail的read
AbstractChannelHandlerContext
public ChannelHandlerContext read() {
// 从tail往前找第一个outBound
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeRead();
} else {
Runnable task = next.invokeReadTask;
if (task == null) {
next.invokeReadTask = task = new Runnable() {
@Override
public void run() {
next.invokeRead();
}
};
}
executor.execute(task);
}
return this;
}
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this; // 指tail
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
从tail往前找第一个outBound,调用其invokeRead
,以上面选取的阶段为例,head->ServerBootstrapAcceptor->tail
,tail前的第一个outBound是head implements ChannelOutboundHandler, ChannelInboundHandler
,它的invokeRead()
实现在AbstractChannelHandlerContext
AbstractChannelHandlerContext:
private void invokeRead() {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).read(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
read();
}
}
调用其本身的read(ChannelHandlerContext ctx)
HeadContext:
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
对于NioServerSocketChannel
它的unsafe指的是NioMessageUnsafe
,beginRead方法实现在AbstractUnsafe
AbstractUnsafe:
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
首先isActive()
由于选取的阶段是服务端启动,定位到NioServerSocketChannel里的实现
public boolean isActive() {
return javaChannel().socket().isBound();
}
java.net.ServerSocket:
/**
* Returns the binding state of the ServerSocket.
*
* @return true 成功绑定地址返回true
* @since 1.4
*/
public boolean isBound() {
// Before 1.3 ServerSockets were always bound during creation
return bound || oldImpl;
}
成功绑定地址isActive
才会返回true。而由于我们选取的阶段时绑定成功后,所以代码执行到这里返回true,doBeginRead()
调用。
AbstractNioMessageChannel:
protected void doBeginRead() throws Exception {
if (inputShutdown) {
return;
}
super.doBeginRead();
}
AbstractNioChannel:
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
对于一个SelectionKey它的isValid()
为true是从其被创建开始一直到它被cancelled或者channel关闭或者Selector关闭位置。
readInterestOp对于NioServerSocketChannel
是OP_ACCEPT,对于NioSocketChannel
是OP_READ。上述代码做的是当相应channel关心的事件被删除则恢复它。
总结一下:我们选取的阶段是绑定完成后的fireChannelActive
的调用,一路分析到这,发现该方法在此阶段的调用目的就是设置NioServerSocketChannel
的关心事件为ACCEPT
在上面HeadContext的channelActive方法中,首先是将channelActive事件传递给下一个inbound,此时pipeline里情况是head->ServerBootstrapAcceptor->tail,下个inbound是ServerBootstrapAcceptor,跟踪发现其并不关心channelActive事件,直接将其向下传递,下一个inbound是tail
TailContext:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
onUnhandledInboundChannelActive();
}
protected void onUnhandledInboundChannelActive() {
}
没做任何处理,channelActive传到尾部就吞掉该事件。
前面分析了服务端启动阶段 invokeHandlerAddedIfNeeded,fireChannelRegistered,fireChannelActive所起的作用。接下来分析它们在连接阶段的作用(连接指客户端连接到来,NioSocketChannel创建过程,具体看我连接一篇的分析)。
对于连接阶段这三个方法全在register0
中被调用。
invokeHandlerAddedIfNeeded:调用前NioSocketChannel的pipeline里的情况是head->ChannelInitialize(指childHandler)->tail,调用后变为head->xxx->xxx->xxx->…(指代码中我们设置的那些childHandler)->tail。
fireChannelRegistered:调用各个handler的channelRegistered
方法。
fireChannelActive:不同于服务端启动,连接阶段执行到这会调用fireChannelActive,因为isActive返回true,具体实现定位到NioSocketChannel
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
channel创建既是open的,关于isConnected()连接一篇中分析过,ServerSocketChannel.accept
返回的SocketChannel
,其在创建过程中状态被赋值为2,即CONNECTED,所以isActive()返回true。
一开始过程与上面的分析一样,直到从tail往前找第一个outBound,调用其invokeRead
,多数outbound的read实现只是将事件往前传,如LengthFieldPrepender,MessageToByteEncoder,IdleStateHandler等,所以如服务端一样最终传到head,head的实现调用unsafe.beginRead();
,对于NioSocketChannel
它的unsafe指的是NioSocketChannelUnsafe
,跟踪发现其实现与上面服务端的一样,只是这里恢复的是NioSocketChannel的OP_READ事件。
连接阶段出现的fireChannelRead,fireChannelReadComplete,以及其它一些将在下一篇介绍。