读写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. 百度SEO算法技术的局限性,怎么做才能有收益

    不知道大家有没有发现,我们使用百度的频率在减少,就算有时遇到一些问题,需要用百度来寻找答案,也会经常遇到搜索不到答案的情况.到底是出了什么问题?难道网络上的资源不够丰富了?浩如烟海的互联网,居然搜索不 ...

  2. 关键字 global和nonlocal

    globale 表示从全局把一个变量(比如a)引入局部,后面的变量全是此变量a 使用   globale 变量名 # 全局变量一般是不能随意的修改的 # a = 10 # def func(): # ...

  3. linux下虚拟环境venv的创建与使用以及virtualenvwrapper

    1.linux安装学习python虚拟环境 linux提供的虚拟环境工具 有virtualenv pipenv 2.我们需求是在linux上可以运行 一个django2 运行一个django1 3.安 ...

  4. JUC的数据库连接池小练习

    JUC练习数据库连接池实现 通过一个连接数组来充当连接池 一个原子的标记数组 通过cas来保持多线程下的安全,用synchronized来进行暂停和唤醒 @Slf4j public class MyC ...

  5. unity---摄像机参数

    2D游戏一般选择填充,减少性能浪费,也一般选择正交模式 Fiel of View 类似望远镜的效果 Clipping Planes 摄像机开始摄像和结束,两个平面的位置 Depth 决定摄像头的优先级 ...

  6. HTML表格以及表单

    学习内容: 1.HTML表格 代码实例: <%@ page language="java" import="java.util.*" pageEncodi ...

  7. 将Abp移植进.NET MAUI项目(一):搭建项目

    ​ 前言 去年12月份做了MAUI混合开发框架的调研,想起来文章里给自己挖了个坑,要教大家如何把Abp移植进Maui项目,由于篇幅限制,将分为三个章节. 将Abp移植进.NET MAUI项目(一):搭 ...

  8. Tarjan入门

    Tarjan系列!我愿称Tarjan为爆搜之王! 1.Tarjan求LCA 利用并查集在一遍DFS中可以完成所所有询问.是一种离线算法. 遍历到一个点时,我们先将并查集初始化,再遍历完一个子树之后,将 ...

  9. HMS Core分析服务6.5.0版本更新啦

    卸载用户价值的合理评估对制定相应的用户召回策略具有重要意义. HMS Core分析服务新版本支持查看用户卸载前使用次数.崩溃次数等指标.通过这些数据,您可以更直观地判断已卸载人群粘性以及崩溃问题对用户 ...

  10. 基于“均态分布”随机数算法的一次性口令OneTimePassword(原创)

    /* 所谓均态分布随机数算法是指:每个数(整数或实数)无序地分布在数轴上,值只出现一次永不重复.体现了香农的一次一密理论. * 均体现在每个数的值是平均概率,即都有出现:态体现在每个数在数轴上的位置是 ...