Netty 源码 Channel(二)核心类

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

相关文章:

1. Channel 类图

2. 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 处理。

3. 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);
}
}

4. 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);
}
}

5. AbstractNioMessageChannel

AbstractNioMessageChannel#doWrite 方法和 AbstractNioByteChannel#doWrite 类似,前者可以写 POJO 对象,后者只能写 ByteBuf 和 FileRegion。

6. 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;
}

7. NioSocketChannel

(1) doConnect

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
} boolean success = false;
try {
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}

连接时有三种情况:

  1. 直接就连接成功,返回 true
  2. 如果没有连接成功,就注册 OP_CONNECT 事件进行监听,返回 false
  3. 发生异常

(2) doWriteBytes

向 ServerSocket 中写入数据。

@Override
protected int doWriteBytes(ByteBuf buf) throws Exception {
final int expectedWrittenBytes = buf.readableBytes();
return buf.readBytes(javaChannel(), expectedWrittenBytes);
}

(3) doReadBytes

从 ServerSocket 中读取数据。

@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}

最底层还是调用 Channel 的 read 方法。

// AbstractByteBuf#writeBytes
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
ensureWritable(length);
int writtenBytes = setBytes(writerIndex, in, length);
if (writtenBytes > 0) {
writerIndex += writtenBytes;
}
return writtenBytes;
}
// UnpooledHeapByteBuf#setBytes
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
ensureAccessible();
try {
return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length));
} catch (ClosedChannelException ignored) {
return -1;
}
}

每天用心记录一点点。内容也许不重要,但习惯很重要!

Netty 源码 Channel(二)核心类的更多相关文章

  1. Netty 源码 Channel(二)主要类

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

  2. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  3. Netty 源码 Channel(一)概述

    Netty 源码 Channel(一)概述 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Channel 为 Netty ...

  4. Netty源码解析 -- 内存对齐类SizeClasses

    在学习Netty内存池之前,我们先了解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务方法. 源码分析基于Netty 4.1.52 Nett ...

  5. Spring源码解读:核心类DefaultListableBeanFactory的继承体系

    1 简介 我们常用的ClassPathXmlApplicationContext是AbstractRefreshableApplicationContext的子类,而DefaultListableBe ...

  6. Mybatis源码解析3——核心类SqlSessionFactory,看完我悟了

    这是昨晚的武汉,晚上九点钟拍的,疫情又一次来袭,曾经熙熙攘攘的夜市也变得冷冷清清,但比前几周要好很多了.希望大家都能保护好自己,保护好身边的人,生活不可能像你想象的那么好,但也不会像你想象的那么糟. ...

  7. netty源码理解(二) serverstrap.bind()

    eventloop是一个线程,里面有一个executor封装了一个线程工厂,在启动的时候启动一个线程,传入的实现了runnable的内部类,里面调用了eventloop的run方法.

  8. Netty 源码解析(二):Netty 的 Channel

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...

  9. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

随机推荐

  1. Snipaste截图

    Snipaste 是一个简单但强大的贴图工具,同时也可以执行截屏.标注等功能. 引自: https://blog.csdn.net/qq_36279445/article/details/702109 ...

  2. Netty 能做什么

    作为一个学Java的,如果没有研究过Netty,那么你对Java语言的使用和理解仅仅停留在表面水平,会点SSH,写几个MVC,访问数据库和缓存,这些只是初等Java程序员干的事.如果你要进阶,想了解J ...

  3. 获取cookie

    1.cookie是存储在用户本地终端的数据,实际上是一小段的文本信息 2.cookie的作用 帮助web站点保存有关的访问者的信息,方便用户的访问,如记住用户名和密码,实现自动登录功能案例:查看访问我 ...

  4. Java 一维多项式计算

    求解Java一维多项式的通用方法 比如ax^4+bx^3+cx^2+dx+e 可以化为(((ax+b)x+c)x+d)x+e 那么观察规律可以将系数放到一个数组里num[e,d,c,b,a] publ ...

  5. MyBufferedReader

    /** 需求:自定义一个包含 readLine 方法的 BufferedReader 来模拟一下 BufferedReader */ import java.io.FileReader; import ...

  6. rancher 2 webhook 格式

    { "version":"4", "groupKey":<string>, "status":"& ...

  7. Oracle 11g 静默安装-db_install.rsp详解

    转自--------------https://blog.csdn.net/jameshadoop/article/details/48086933 :db_install.rsp详解 ####### ...

  8. unity 3d音效如何设置?,近大远小

    请问怎么将导入的音频文件设置为“3D Sound”?这是我导入的音频文件: 现在3D音效的设置不在导入音效文件的地方,而是在AudioSource的SpatialBlend属性,0表示2D,1表示3D ...

  9. TZOJ 3711 浪漫自习(最大流)

    描述 如今的校园谈恋爱已是习以为常,这不,去上自习也要成双成对的.现在假设某班里有N对情侣从同一寝室楼出发,到达同一个教室上自习.途中,她们可能会经过长廊.静溪等一系列的景点观光游览.但情侣们不希望在 ...

  10. Django的具体操作(二)

    今日内容:用户登录以及分页的实现 views.py # 登录动作 def login_action(request): # 必须继承request if request.method == 'POST ...