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 ...
随机推荐
- Struts1框架学习笔记
类实现DispatchAction 类似于ActionServlet ActionServlet 来自于 org.apache.struts.action 包,它继承自 HttpServlet, ...
- Nginx高级应用之Location Url
基本配置 为了探究nginx的url配置规则,当然需要安装nginx.我使用了vagrant创建了一个虚拟环境的Ubuntu,通过apt-get安装nginx.这样就不会污染mac的软件环境.通过vr ...
- 矩形覆盖(python)
题目描述 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形.请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? # -*- coding:utf-8 -*- class S ...
- Maven 添加jar包到本地仓库
一.使用Maven命令安装jar包 前提:在windows操作系统中配置好了Maven的环境变量,怎么配置请自己百度,这里不介绍,可参考https://jingyan.baidu.com/articl ...
- FormData的使用
var formData = new FormData(); <form id="coords" class="coords" onsubmit=&quo ...
- Appium+python自动化4-等待函数
4.1 等待函数癈使用 4.1.1 为什么要使用等待函数 我们在做自动化的时候很多时候都不是很顺利,不是因为app的问题,我们的脚本也没问题,但是很多时候都会报错,比如一个页面本来就有id为1的这个元 ...
- this.class.getClassLoader().getResourceAsStream与this.class.getResourceAsStream 文件地址获取
本文部分转自:http://xixinfei.iteye.com/blog/1256291 this.getClass().getClassLoader().getResource("tem ...
- Mac Sublime Text3 如何安装插件
1.打开sublime text3后按快捷键control+`后下面会出来东西,然后输入如下命令. import urllib.request,os; pf = 'Package Control.su ...
- Springboot学习03-SpringMVC自动配置
Springboot学习03-SpringMVC自动配置 前言 在SpringBoot官网对于SpringMVCde 自动配置介绍 1-原文介绍如下: Spring MVC Auto-configur ...
- sql2000三个表的级联删除
sql2000中三个表级联删除 create table a( id int primary key, Content varchar(50)) create table b( id ...