Netty 源码 Channel(二)主要类
Netty 源码 Channel(二)主要类
Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)
一、Channel 类图

二、AbstractChannel
2.1 几个重要属性
// SocketChannel 的 parent 是 ServerSocketChannel
private final Channel parent;
// 唯一标识
private final ChannelId id;
// Netty 内部使用
private final Unsafe unsafe;
// pipeline
private final DefaultChannelPipeline pipeline;
// 绑定的线程
private volatile EventLoop eventLoop;
protected AbstractChannel(Channel parent, ChannelId id) {
this.parent = parent;
this.id = id;
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
2.2 核心 API
read、write、connect、bind 都委托给了 pipeline 处理。
三、AbstractNioChannel
3.1 几个重要属性
// NIO 底层 Channel
private final SelectableChannel ch;
// 感兴趣的事件
protected final int readInterestOp;
// 绑定的 SelectionKey,当 selectionKey 修改后其它线程可以感知
volatile SelectionKey selectionKey;
3.2 核心 API
(1) doRegister
将 channel 注册到 eventLoop 线程上,此时统一注册的感兴趣的事件类型为 0。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 1. 将 channel 注册到 eventLoop 线程上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// 2. 对注册失败的 channel,JDK 将在下次 select 将其删除
// 然而此时还没有调用 select,当然也可以调用 selectNow 强删
eventLoop().selectNow();
selected = true;
} else {
// 3. JDK API 描述不会有异常,实际上...
throw e;
}
}
}
}
(2) doBeginRead
doBeginRead 只做了一件事就是注册 channel 感兴趣的事件。此至就可以监听网络事件了。
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
四、AbstractNioByteChannel
AbstractNioByteChannel 中最重要的方法是 doWrite,我们一起来看一下:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
// 1. spin 是自旋的意思,也就是最多循环的次数
int writeSpinCount = config().getWriteSpinCount();
do {
// 2. 从 ChannelOutboundBuffer 弹出一条消息
Object msg = in.current();
if (msg == null) {
// 3. 写完了就要清除半包标记
clearOpWrite();
// 4. 直接返回,不调用 incompleteWrite 方法
return;
}
// 5. 正确处理了一条 msg 消息,循环次数就减 1
writeSpinCount -= doWriteInternal(in, msg);
} while (writeSpinCount > 0);
// 6. writeSpinCount < 0 认为有半包需要继续处理
incompleteWrite(writeSpinCount < 0);
}
为什么要设置最大自旋次数,一次把 ChannelOutboundBuffer 中的所有 msg 处理完了不是更好吗?如果不设置的话,线程会一直尝试进行网络 IO 写操作,此时线程无法处理其它网络 IO 事件,可能导致线程假死。
下面我们看一下 msg 消息是如何处理的,这里以 ByteBuf 消息为例:
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
// 1. 不可读则丢弃这条消息,继续处理下一条消息
if (!buf.isReadable()) {
in.remove();
return 0;
}
// 2. 由具体的子类重写 doWriteBytes 方法,返回处理了多少字节
final int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount > 0) {
// 3. 更新进度
in.progress(localFlushedAmount);
if (!buf.isReadable()) {
in.remove();
}
return 1;
}
// 文件处理,这里略过,类似 ByteBuf
} else if (msg instanceof FileRegion) {
// 省略 ...
} else {
throw new Error();
}
return WRITE_STATUS_SNDBUF_FULL; // WRITE_STATUS_SNDBUF_FULL=Integer.MAX_VALUE
}
doWriteBytes 进行消息发送,它是一个抽象方法,由具体的子类实现。如果本次发送的字节数为 0,说明发送的 TCP 缓冲区已满,发生了 ZERO_WINDOW。此时再次发送可能仍是 0,空循环会占用 CPU 资源。因此返回 Integer.MAX_VALUE。直接退出循环,设置半包标识,下次继续处理。
// 没有写完,有两种情况:
// 一是 TCP 缓冲区已满,doWriteBytes 定入 0 个字节,导致 doWriteInternal 返回 Integer.MAX_VALUE,
// 这时设置了半包标识,会自动轮询写事件
// 二是自旋的次数已到,将线程交给其它任务执行,未写完的数据通过 flushTask 继续写
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
if (setOpWrite) {
setOpWrite();
} else {
// Schedule flush again later so other tasks can be picked up in the meantime
Runnable flushTask = this.flushTask;
if (flushTask == null) {
flushTask = this.flushTask = new Runnable() {
@Override
public void run() {
flush();
}
};
}
eventLoop().execute(flushTask);
}
}
最后我们来看一下半包是如何处理的,可以看到所谓的半包标记其实就是是否取 OP_WRITE 事件。
protected final void clearOpWrite() {
final SelectionKey key = selectionKey();
final int interestOps = key.interestOps();
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
}
protected final void setOpWrite() {
final SelectionKey key = selectionKey();
final int interestOps = key.interestOps();
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
}
五、AbstractNioMessageChannel
AbstractNioMessageChannel#doWrite 方法和 AbstractNioByteChannel#doWrite 类似,前者可以写 POJO 对象,后者只能写 ByteBuf 和 FileRegion。
六、NioServerSocketChannel
NioServerSocketChannel 通过 doReadMessages 接收客户端的连接请求:
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
return 0;
}
七、NioSocketChannel
每天用心记录一点点。内容也许不重要,但习惯很重要!
Netty 源码 Channel(二)主要类的更多相关文章
- Netty 源码 Channel(二)核心类
Netty 源码 Channel(二)核心类 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.Channel 类图 二. ...
- Netty 源码(二)NioEventLoop 之 Channel 注册
Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...
- Netty 源码 Channel(一)概述
Netty 源码 Channel(一)概述 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Channel 为 Netty ...
- Netty源码解析 -- 内存对齐类SizeClasses
在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法. 源码分析基于Netty 4.1.52 Nett ...
- netty源码理解(二) serverstrap.bind()
eventloop是一个线程,里面有一个executor封装了一个线程工厂,在启动的时候启动一个线程,传入的实现了runnable的内部类,里面调用了eventloop的run方法.
- Netty 源码解析(二):Netty 的 Channel
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...
- Netty 源码解析(八): 回到 Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(六): Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一 ):开始 Netty ...
- 【Netty源码分析】ChannelPipeline(二)
在上一篇博客[Netty源码学习]ChannelPipeline(一)中我们只是大体介绍了ChannelPipeline相关的知识,其实介绍的并不详细,接下来我们详细介绍一下ChannelPipeli ...
随机推荐
- mui-当使用addeleventlisener()方法绑定事件时选择器无法绑定事件
在mui中绑定事件不能用jQuery或mui(“#XX”)的形式选取某个元素,而是document.getelementbyid()的形式 mui(“#XX”)可以使用on方法绑定事件
- Choosing the Type at Runtime
[Choosing the Type at Runtime] You cannot use a general expression as the React element type. If you ...
- python网络编程之互斥锁
标签(空格分隔): 互斥锁 进程之间的数据不共享,但是共享同一套文件系统,所以访问同一个文件,或者同一个打印终端,是没有问题的,而共享带来的问题就是竞争,竞争带来的结果就是错乱,如下: #并发运行,效 ...
- python全栈开发 随笔 'is' 和 == 的比较知识与区别 编码和解码的内容及转换
python 一. is 和 == 的区别; == 比较的是两边的值. a = 'alex' b = 'alex' print(a = b) #True a = 10 b = 10 print(a = ...
- char、varchar、nchar、nvarchar的区别(转)
转自:http://tech.diannaodian.com/dw/data/sql/2011/1005/135556.html 对于程序中的string型字段,SQLServer中有char.var ...
- python websocket网页实时显示远程服务器日志信息
功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上的日志信息 一般我们在运维工具部署环境的时候,需要实时展现部署过程中的信息,或者在浏览器中实时显示程序日志给开发人员看.你还在用 ...
- Django2.1在根据models生成数据库表时报 __init__() missing 1 required positional argument: 'on_delete'
解决办法: a=models.ForeignKey('BookInfo',on_delete=models.CASCADE,) 即在外键值的后面加上 on_delete=models.CASCADE ...
- Python delattr() 函数
Python delattr() 函数 Python 内置函数 描述 delattr 函数用于删除属性. delattr(x, 'foobar') 相等于 del x.foobar. 语法 dela ...
- Android 实现简单 倒计时60秒,一次1秒
倒计时功能如上图所示,其实就几行代码即可实现效果啦!!! /** 倒计时60秒,一次1秒 */ CountDownTimer timer = new CountDownTimer(60*1000, 1 ...
- int 和 Integer 的区别
1.两个New生成的Integer 永远不相等,因为他们的内存地址不相等 2.如果一个是New生成的Integer 另一个是通过赋值生成的话,如果值相等那么他们相等,因为这时Integer会通过自动拆 ...