读写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,看图:

    忽略前面的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的管道如下图:

在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

Netty源码解读(四)-读写数据的更多相关文章

  1. Netty源码解读(一)-前置准备

    前置条件 源码版本netty4.1 了解Java NIO.Reactor模型和Netty的基本使用. 解释一下: Java NIO:了解BIO和NIO的区别以及Java NIO基础API的使用 Rea ...

  2. 【Netty源码分析】发送数据过程

    前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...

  3. Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

    本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...

  4. Bert系列 源码解读 四 篇章

    Bert系列(一)——demo运行 Bert系列(二)——模型主体源码解读 Bert系列(三)——源码解读之Pre-trainBert系列(四)——源码解读之Fine-tune 转载自: https: ...

  5. Netty源码解读(二)-服务端源码讲解

    简单Echo案例 注释版代码地址:netty 代码是netty的源码,我添加了自己理解的中文注释. 了解了Netty的线程模型和组件之后,我们先看看如何写一个简单的Echo案例,后续的源码讲解都基于此 ...

  6. Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

    本期内容 : 数据接收架构设计模式 数据接收源码彻底研究 一.Spark Streaming数据接收设计模式   Spark Streaming接收数据也相似MVC架构: 1. Mode相当于Rece ...

  7. Python Web Flask源码解读(四)——全局变量

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  8. mybatis源码解读(四)——事务的配置

    上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...

  9. go语言 nsq源码解读四 nsqlookupd源码options.go、context.go和wait_group_wrapper.go

    本节会解读nsqlookupd.go文件中涉及到的其中三个文件:options.go.context.go和wait_group_wrapper.go. options.go 123456789101 ...

随机推荐

  1. slice-substring-substr-split-splice

    一.字符串slice(startIndex, endBeforeIndex) endBeforeIndex < 0,则 endBeforeIndex 为 负数,第二个参数为字符串长度加上负值,之 ...

  2. Apache Shiro 漏洞汇总

    Apache Shiro 漏洞汇总 以下是我个人通过收集信息收集起来的一些Apache Shiro漏洞信息,这些漏洞的poc都是公开的,利用起来也是比较简单 Apache Shiro是什么东西: Ap ...

  3. 如何使用 Javascript 将图标字体渲染为图片

    前言 在软件开发中肯定要用到图标,比如下图的 Groove 音乐中就用到了许多图标.一种获取这些图标的方法是把 Groove 音乐截个图,然后熟练地开启 Photoshop,开始抠图.这种方式很逊,效 ...

  4. CMake技术总结

    在做算法部署的过程中,我们一般都是用C++开发,主要原因是C++的高效性,而构建维护一个大型C++工程的过程中,如何管理不同子模块之间的依赖.外部依赖库.头文件和源文件如何隔离.编译的时候又该如何相互 ...

  5. 通过python将阿里云DNS解析作为DDNS使用

    通过python将阿里云DNS解析作为DDNS使用 脚本需要Python2.x运行 安装alidns python sdk sudo pip install aliyun-python-sdk-ali ...

  6. 用Repo管理自己的本地仓库

    AOSP使用Repo工具管理项目源码.而Repo工具则依赖一个名叫manifest的git仓库来记录Android源码中都包含哪些子仓库. 进入Android源码根目录下的.repo目录,可以看到ma ...

  7. python之数据类型的内置方法(set、tuple、dict)与简单认识垃圾回收机制

    目录 字典的内置方法 类型转换 字典取值 修改值 计算字典长度 成员运算 删除元素 获取元素 更新字典 快速生成字典 setdefault()方法 元组的内置方法 类型转换 索引与切片操作 统计长度 ...

  8. uni-app 中实现 onLaunch 异步回调后执行 onLoad 最佳实践

    前言 好久没写博客了,由于公司业务需要,最近接触uiapp比较多,一直想着输出一些相关的文章.正好最近时间富余,有机会来一波输出了. 问题描述 在使用 uni-app 开发项目时,会遇到需要在 onL ...

  9. 微信小程序避坑指南——input框里的图标在部分安卓机里无法点击的问题

    问题场景: 下图中的显隐密码和验证码均为包裹在 input标签 中的 image标签, 但在开发测试中发现点击不了这俩个image标签,因为是被input标签的padding挡住了. 解决方法:将im ...

  10. 【HEOI2014】大工程<虚树>

    虚树 我们每天都用心思索着,这究竟是为了什么呢?我想我也不知道,只是觉得如果人不思考问题就很无聊. 我觉得虚树不是什么数据结构,就是一种技巧或者工具.它能把树中\(k\)个关键点以\(O(klogk) ...