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. win10 壁纸路径

    C:\用户\用户名\AppData\Roaming\Microsoft\Windows\Themes\CachedFiles 原文: https://blog.csdn.net/qq_35040828 ...

  2. MySql数据库常用语句汇总

    第一天1.登陆数据库 mysql -uroot -proot; //-u用户名 -p密码2.启动数据库 net start mysql;3.创建表空间(数据库)create database qy97 ...

  3. Java中的字节流,字符流,字节缓冲区,字符缓冲区复制文件

     一:创建方式 1.建立输入(读)对象,并绑定数据源 2.建立输出(写)对象,并绑定目的地 3.将读到的内容遍历出来,然后在通过字符或者字节写入 4.资源访问过后关闭,先创建的后关闭,后创建的先关闭 ...

  4. MS17-010漏洞检测

    1.扫描脚本的下载和加载 由于Metasploit还没有更新MS17-010检测的模块,所以要去exploit-db下载,并在MSF中加载. cd /usr/share/metasploit-fram ...

  5. 制作u盘kali系统启动盘

    准备好一个容量大于8G的u盘,和kali系统的镜像文件. 下载universal-usb-install软件,打开设置如下,create等待几分钟. 下载minitool分区工具,插入u盘,打开min ...

  6. Oracle中dbms_random.string 的用法

    转载:https://blog.csdn.net/simonchi/article/details/8657787 DBMS_RANDOM.STRING(var1,var2) 这个函数有两个参数 va ...

  7. CentOS 7安装Zabbix 3.4

    01.最小化安装操作系统 02.升级系统组件到最新版本 yum -y update 03.关闭 SELinux sed -i “s/SELINUX=enforcing/SELINUX=disabled ...

  8. CentOS 下搭建Gitlab

    centos7安装部署gitlab服务器   我这里使用的是centos 7 64bit,我试过centos 6也是可以的! 1. 安装依赖软件 yum -y install policycoreut ...

  9. MongoDB之Array Object的特殊操作

    相比关系型数据库,Array[1,2,3,4,5]和Object{'name':'Wjs'}是MongoDB比较特殊的类型 db.Wjs.insert({"name":" ...

  10. 18. 4Sum (通用算法 nSum)

    Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = tar ...