Netty源码解读(四)-读写数据

读写Channel(READ)的创建和注册

在NioEventLoop#run中提到,当有IO事件时,会调用processSelectedKeys方法来处理。

当客户端连接服务端,会触发服务端的ACCEPT事件,创建负责READ事件的channel并注册到workerGroup中

跟踪processSelectedKeys的调用

NioEventLoop#processSelectedKeys()
-->
NioEventLoop#processSelectedKeysOptimized()
-->
NioEventLoop#processSelectedKey(SelectionKey k, AbstractNioChannel ch)
-->
// AbstractNioMessageChannel#read()
public void read() {
。。。。。。
try {
try {
do {
// 用于读取bossGroup中EventLoop的NIOServerSocketChannel接收到的请求数据,并把这些请求数据放入到readBuf
// 结束后,readBuf中存放了一个处理客户端后续请求的NioSocketChannel
// 与java nio对应的就是serverSocketChannel的accept生成SocketChannel,并封装成NioSocketChannel放入到readBuf中
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (continueReading(allocHandle));
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 核心功能
// 依次触发NioServerSocketChannel的pipeline中所有入站Handler中的channelRead()方法的执行
// 注意:此处还是在bossGroup的线程,不是workGroup
// 所以,执行可能是LoggingHandler
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
// 触发管道中所有handler的channelReadComplete方法
pipeline.fireChannelReadComplete();
。。。。。。
} finally {
。。。。。。
}
}

这里主要关注两个方法:

  1. doReadMessages

    调用Java NIO的API,获取ACCEPT产生的SocketChannel,并封装成NioSocketChannel

    protected int doReadMessages(List<Object> buf) throws Exception {
    // 调用服务端ServerSocketChannel的accept方法产生一个处理客户端后续请求的SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
    if (ch != null) {
    // 将这个SocketChannel封装成NioSocketChannel添加到buf容器中
    buf.add(new NioSocketChannel(this, ch));
    return 1;
    }
    } catch (Throwable t) {
    。。。。。。
    }
    return 0;
    }
  2. pipeline.fireChannelRead

    依次触发管道中所有入站Handler中的channelRead()方法(从HeadContext开始)。

    再次复习下管道中的所有Handler,看图:

    Untitled

    忽略前面的Handler,直接来到ServerBootstrapAcceptor

    // 类ServerBootstrapAcceptor
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    // 添加用户自定义的handler
    child.pipeline().addLast(childHandler);
    // 设置相关属性
    setChannelOptions(child, childOptions, logger);
    setAttributes(child, childAttrs);
    try {
    // 将channel注册到workerGroup的EventLoop
    childGroup.register(child).addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    if (!future.isSuccess()) {
    forceClose(child, future.cause());
    }
    }
    });
    } catch (Throwable t) {
    forceClose(child, t);
    }
    }

    到了childGroup.register这里,就和前面bossGroup的channel注册一样了,前面的代码长这样config().group().register,请擅用搜索。

    区别在于,注册进bossGroup的是NioServerSocketChannel,负责ACCEPT事件。

    注册进workerGroup的是NioSocketChannel,负责READ事件。

    小结

    客户端连接时,触发ACCEPT事件(在bossGroup中),生成NioSocketChannel并注册进workerGroup的EventLoop中。然后触发READ事件(在workerGroup中)进行读写数据。

往通道写入数据

demo中的workerGroup中的channel的管道如下图:

Untitled

在netty的管道pipeline中,头尾是固定的,addLast方法,插入的handler在tail前

head的类是HeadContext,类型是in、out

Tail的类是TailContext,类型是in

有两种方式写入数据

  • channelHandlerContext.write()
  • channel.write()

区别在于:第一种是从管道当前位置往前找,第二种从tail往前找

比如在MyEchoHandler中使用channelHandlerContext.write(),则路径是

MyEchoHandler → HeadContext

如果使用channel.write(),路径是

TailContext → MyEchoHandler → HeadContext

源码跟踪路径:

  1. ctx.write()

    AbstractChannelHandlerContext#write(Object msg)-->
    AbstractChannelHandlerContext#write(final Object msg, final ChannelPromise promise)-->
    AbstractChannelHandlerContext#write(Object msg, boolean flush, ChannelPromise promise)-->
    AbstractChannelHandlerContext#invokeWrite(Object msg, ChannelPromise promise)-->
    AbstractChannelHandlerContext#invokeWrite0(Object msg, ChannelPromise promise)-->
    // 一个一个outboundHandler往前调用write,直到HeadContext
    HeadContext#write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)-->
    AbstractUnsafe#write(Object msg, ChannelPromise promise)
  2. ctx.channel().write()

    AbstractChannel#write(Object msg)-->
    DefaultChannelPipeline#write(Object msg)-->
    // TailContext继承自AbstractChannelHandlerContext
    AbstractChannelHandlerContext#write(Object msg)-->
    // 这里就和ctx.write()一样了

注意:

write只是将内容写入到channel的缓存ChannelOutboundBuffer中,并且会判断如果大小大于高水位,会将channel置为不可写(isWritable判断)

想要写入到socket,需要调用flush方法

即使调用writeAndFlush,效果也是先执行全部outboundHandler的write,再执行flush

posted @   王谷雨  阅读(175)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示