先上官方图
* I/O Request
* via {@link Channel} or
* {@link 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() ] |
* | |
* | Netty Internal I/O Threads (Transport Implementation) |
* +-------------------------------------------------------------------+
这里有一点注意的是往哪是IN,往哪是OUT,
往程序中入的IN 即socket —> 程序 从程序往外走的是OUT 即 程序 —> socket
上图体现了ChannelPipeLine ChannelHandler ChannelHandlerContext 三者的关系,
pipeline是他们的整体,是一条执行链
handler 是用户定义的,处理响应信息和请求信息的的处理器,其中包含的是具体的执行逻辑
context 是沟通handler和pipeline的连接器,执行链的事件向下传递,需要context链表向下next,事件的处理需要context来调用handler的执行逻辑
这里最需要搞明白的就是,事件是怎么沿着执行链向下传递,依次调用各个handler的执行逻辑的
本来我以为pipeline在收到请求后就会从头context开始,一个一个context去next去调用handler的方法,其传递的过程会在context中写好了,但其实并不是。
以inbound的read事件为例,你往pipeline中addLast多个handler,都重写了channelRead方法,运行时,read事件触发后并不会自动经过所有的handler的channelRead方法,需要我们在上一个channelRead中手动将事件传递给下一个cxt,这个read事件才可以触发多个handler的channelRead方法。
也就是说,如果我们不在handler中做合适的处理,向下next context 那么pipeline就无法向下传递事件,那么下面的handler就起不到作用了。
以代码为例,这里我们自定义了两个handler都add到pipeline中
b.group(bossGroup, workerGroup)
//5:需要指定使用NioServerSocketChannel这种类型的通道
.channel(NioServerSocketChannel.class)//(3) 服务端 -->NioServerSocketChannel
//6:一定要使用childHandler 去绑定具体的事件处理器
.childHandler(new ChannelInitializer<SocketChannel>() //(4) childHandler
{
@Override
protected void initChannel(SocketChannel sc) throws Exception
{
sc.pipeline().addLast(new ServerHandler2());
sc.pipeline().addLast(new ServerHandler1());//handler中实现真正的业务逻辑
}
})
handler1
public class ServerHandler1 extends ChannelInboundHandlerAdapter
{
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("handler1 complete read");
ctx.flush();
}
/**
* 每当从客户端收到新的数据时,这个方法会在收到消息时被调用
* ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
* 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
try{
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data,"utf-8");
System.out.println("hanlder1:"+request);
ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()))
.addListener(ChannelFutureListener.CLOSE);//添加监听 当服务端向客户端发送完数据后,关闭connect连接
ctx.close();
}finally{
// Discard the received data silently.
ReferenceCountUtil.release(msg);
}
}
}
handler2
public class ServerHandler2 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler2 is reading");
//do something
//接收客户端发送的数据 ByteBuf
ByteBuf buf = (ByteBuf)msg;
//创建一个和buf长度一样的空字节数组
byte[] data = new byte[buf.readableBytes()];
//将buf中的数据读取到data数组中
buf.readBytes(data);
//将data数据包装成string输出
String request = new String(data,"utf-8");
System.out.println("hanlder2 :" + request);
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("handler2 complete read");
super.channelReadComplete(ctx);
}
}
然后打开服务器使用客户端请求,可以看到如下输出,这里我们可以看到,读事件经过了这个两个handler
handler2 is reading
hanlder2 :execute
hanlder1:
handler2 complete read
handler1 complete read
但如果我把handler2中的
//super.channelRead(ctx, msg);
注释掉,再次运行
handler2 is reading
hanlder2 :execute
handler2 complete read
我们就可以发现,handler2后面的handler根本就没有执行
所以handler之间的事件传递,需要开发者手动进行
下面接着EventLoop中read事件触发后processSelectedKey的read处理函数来查看pipeline中事件是如何在handler之间传递的
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();//read事件触发,调用channel的read处理函数
}
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
//pipeline开始从head处理
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
//read结束后,处理函数
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
}
}
上面channel的read方法中主要就是两个部分:
1、将数据读到bytebuf中,然后传入到pipeline中,pipeline.fireChannelRead(byteBuf),使用 handler对数据进行处理
2、数据完毕后,调用pipeline.fireChannelReadComplete();进行读取完毕处理
先看 pipeline.fireChannelRead(byteBuf)
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
到了这里,接下来就是调用handler中的channelRead(this, msg)方法了,到了这里,只执行了一个hanlder中的channelRead方法,然后,就没有了???说好的执行链呢,所以到这我们就可以看出来,context的向后传递不是由系统框架自动实现的,需要我们在自定义的handler中手动向后传递, 下面我们来看看,自定义的handler中如何传递事件的,
从上面的测试例子中可以看到,super.channelRead(ctx, msg) 是实现pipeline中事件传递的关键,所以我们从这个方法看起
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
public ChannelHandlerContext fireChannelRead(Object msg) {
invokeChannelRead(this.findContextInbound(), msg);//关键的find方法
return this;
}
到这,我们可以看到一个事件传递的重要方法findContextInbound()
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while(!ctx.inbound);//循环,直到找到下一个inbound的handler
return ctx;
}
到这终于真相大白了
pipeline中事件传递方式就是,事件触发后,先从head开始,向下找到第一个能处理事件的handler,当handler1处理完后,如果在handler1中不手动将事件向下传递,那么事件就终止在当前handler,其他handler中的处理函数就不会触发。如果每个handler都使用super.fire()方法的话,那么事件会在所有的handler中传递一遍。