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 {
。。。。。。
}
}
这里主要关注两个方法:
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;
}
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
源码跟踪路径:
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)
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源码解读(四)-读写数据的更多相关文章
- Netty源码解读(一)-前置准备
前置条件 源码版本netty4.1 了解Java NIO.Reactor模型和Netty的基本使用. 解释一下: Java NIO:了解BIO和NIO的区别以及Java NIO基础API的使用 Rea ...
- 【Netty源码分析】发送数据过程
前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...
- Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考
本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...
- Bert系列 源码解读 四 篇章
Bert系列(一)——demo运行 Bert系列(二)——模型主体源码解读 Bert系列(三)——源码解读之Pre-trainBert系列(四)——源码解读之Fine-tune 转载自: https: ...
- Netty源码解读(二)-服务端源码讲解
简单Echo案例 注释版代码地址:netty 代码是netty的源码,我添加了自己理解的中文注释. 了解了Netty的线程模型和组件之后,我们先看看如何写一个简单的Echo案例,后续的源码讲解都基于此 ...
- Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考
本期内容 : 数据接收架构设计模式 数据接收源码彻底研究 一.Spark Streaming数据接收设计模式 Spark Streaming接收数据也相似MVC架构: 1. Mode相当于Rece ...
- Python Web Flask源码解读(四)——全局变量
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- mybatis源码解读(四)——事务的配置
上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...
- go语言 nsq源码解读四 nsqlookupd源码options.go、context.go和wait_group_wrapper.go
本节会解读nsqlookupd.go文件中涉及到的其中三个文件:options.go.context.go和wait_group_wrapper.go. options.go 123456789101 ...
随机推荐
- 老生常谈系列之Aop--前言
老生常谈系列之Aop--前言 前言 既然是前言,那么这一篇就不会写具体的技术问题.这篇文章主要记录我一些个人的思考以及为什么要写文章的缘由.前不久在跟朋友的交流中偶然聊到了Aop,Aop全称为 Asp ...
- 【Azure Redis 缓存】 Python连接Azure Redis, 使用redis.ConnectionPool 出现 "ConnectionResetError: [Errno 104] Connection reset by peer"
问题描述 Python连接Azure Redis, 使用redis.ConnectionPool 出现 "ConnectionResetError: [Errno 104] Connecti ...
- zabbix 线路质量监控自定义python模块,集成ICMP/TCP/UDP探测,批量监控线路质量自定义阈值联动mtr保存线路故障日志并发送至noc邮箱
互联网故障一般表现为丢包和时延增大,持续性故障不难排查,难的是间歇性或凌晨故障,后者往往来不及等我们测试就已经恢复正常,得不到异常时的mtr无法判断故障点在哪里 故此有了根据丢包率和时延变换联动mtr ...
- 128_Power BI父级排名TOPN子级动态展示
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 前些天在和朋友交流Power BI中有这样一个需求,按照父级排名后,需要显示出父级TOPN的子级明细. 如下&l ...
- 题解 P7075 [CSP-S2020] 儒略日
当时考场上因为这个炸掉,一年后回来复仇. 这里提供一个与大多数人不一样的做法. 首先考虑一个简单一些的问题,怎么应付单个询问? 不难想到,我们对于一个日期,让他从 \(-4713\) 年 \(1\) ...
- IDEA windows版本快捷键
使用本快捷键前,可以在idea使用下面方法确认版本! Ctrl 快捷键 介绍 Ctrl + F 在当前文件进行文本查找 (必备)Ctrl + R 在当前文件进行文本替换 (必备) Ctrl + Z 撤 ...
- git 无法拉取最新代码
删除本地文件后,想从远程仓库中重新新Pull最新代码,但是执行了git pull origin develop 命令后始终无法拉取下来 提示 Already up-to-date. 原因:当前本地库处 ...
- python基础学习7
python基础学习7 内容概要 字符串的内置方法 字符串的内置方法(补充) 列表的内置方法 可变类型与不可变类型 队列与堆栈 内容详情 字符串的内置方法 # 1.strip 移除字符串首尾的指定字符 ...
- springboot引入mybatis遇到的坑
前边分享了springboot项目的创建及springboot项目的默认配置文件等,想温习的小伙伴可移步至文章末尾阅读,感谢.今天来分享下springboot引入mybatis框架的步骤,有小伙伴 ...
- 阿里云FTP服务配置
阿里云的CENTOS 7.4 并没有开启防火墙服务 所以好多人配置了FTP后会出现各种不能访问的问题 关键原因在于端口没有开放.设置端口阿里云ECS的管理控制台中"安全组" &qu ...