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 ...
随机推荐
- go ants源码分析
golang ants 源码分析 结构图 poolwithfunc与pool相差不大,这里我们只分析ants默认pool的流程 文件 作用 ants.go 定义常量.errors显示.默认建一个大小为 ...
- mysql5.6 innodb_large_prefix引起的一个异常
phenomenon: Specified key was too long; max key length is 3072 bytes 在修改一个数据库字段时,字段容量被限制为了表前缀的大小而不是本 ...
- ES6片段
那些可能会忘记或不知所以然的点: 1. 2. 3. 4. 5. 6. /* 函数调用会在内存形成一个"调用记录",又称调用帧,保存调用位置和内存变量等信息. 如果在函数 A 的内部 ...
- Nginx的常用配置
Nginx配置文件结构 设置worker进程的用户,指的linux中的用户,会涉及到nginx操作目录或文件的一些权限,默认为 nobodyuser root; worker进程工作数设置,一般来说C ...
- 我用13行摆烂了你的oj4
13行代码AC oj4是怎么回事呢?13行代码AC oj4相信大家都很熟悉,但是13行代码AC oj4是怎么回事呢,下面就让小编带大家一起了解吧.13行代码AC oj4,其实就是13行代码AC oj4 ...
- 69. Sqrt(x) - LeetCode
Question 69. Sqrt(x) Solution 题目大意: 求一个数的平方根 思路: 二分查找 Python实现: def sqrt(x): l = 0 r = x + 1 while l ...
- 好客租房30-事件绑定this指向(箭头函数)
1箭头函数 利用箭头函数自身不绑定this的特点 //导入react import React from 'react' import ReactDOM from 'rea ...
- pyecharts世界地图用:国家中英文对照表.xlsx
用pyecharts画Map或者Geo,需要用到的国家中英文对照表: 英文 中文 Zimbabwe 津巴布韦 Zambia 赞比亚 Yugoslavia 南斯拉夫 Yemen 也门 Western S ...
- HTML表格以及表单
学习内容: 1.HTML表格 代码实例: <%@ page language="java" import="java.util.*" pageEncodi ...
- 我是一个Dubbo数据包...
hello,大家好呀,我是小楼! 今天给大家带来一篇关于Dubbo IO交互的文章,本文是一位同事写的文章,用有趣的文字把枯燥的知识点写出来,通俗易懂,非常有意思,所以迫不及待找作者授权然后分享给大家 ...