Netty4简单认知

2023-11-07

Channel简介

在Netty中,Channel相当于一个Socket的抽象,它为用户提供了关于Socket状态(是连接还是断开)及对Socket的读、写等操作。每当Netty建立了一个连接,都创建一个与其对应的Channel实例。

Channel的注册过程所做的工作就是将Channel与对应的EventLoop进行关联。因此,在Netty中,每个Channel都会关联一个特定的EventLoop,并且这个Channel中的所有I/O操作都是在这个EventLoop中执行的;当关联好Channel和EventLoop后,会继续调用底层JavaNIO的SocketChannel对象的register()方法,将底层Java NIO的SocketChannel注册到指定的Selector中。通过这两步,就完成了Netty对Channel的注册过程。

而对于Channel的创建过程中,会传入参数Channel创建ChannelPipeline,创建ChannelPipeline时候会创建ChannelHandlerContext传入ChannelPipeline。


下图表示常用Channel:

NioSocketChannel的创建

    Bootstrap是Netty提供的一个便利的工厂类,可以通过它来完成客户端或服务端的Netty初始化。先来看一个例子,从客户端程序是如何启动的。首先,从客户端的代码片段开始。

package com.example.gateway;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyChatClient {
    public NettyChatClient connect(int port, String host, final String nickName) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;
    }
}

在实例化NioSocketChannel的过程中,Unsafe就特别关键。Unsafe其实是对Java底层Socket操作的封装,因此,它实际上是沟通Netty上层和Java底层的重要桥梁。

  • NioSocketChannel创建过程(只是将构造函数赋值给了bootstrapt,实例化过程需要在bootstrap.connect链接时构建):

(1)调用NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个新的Java NioSocketChannel。

(2)初始化AbstractChannel(Channel parent)对象并给属性赋值,具体赋值的属性如下。

      id:每个Channel都会被分配一个唯一的id。

      parent:属性值默认为null。

      unsafe:通过调用newUnsafe()方法实例化一个Unsafe对象,它的类型是AbstractNioByteChannel.NioByteUnsafe内部类。

      pipeline:是通过调用new DefaultChannelPipeline(this)新创建的实例,而unsafe。DefaultChannelPipeline中还有两个特殊的属性,即Head和Tail,这两个属性是双向链表的头和尾。其实在DefaultChannelPipeline中维护了一个以AbstractChannelHandlerContext为节点元素的双向链表,这个链表是Netty实现Pipeline机制的关键。

// DefaultChannelPipeline的依赖
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
// 构造函数的初始化
tail = new TailContext(this);
head = new HeadContext(this);

pipeline在connect的时候初始化init channel过程中注册被封装了ChannerHandler的AbstractChannelHandlerContext类,这个注册的过程是: tail.prev = newCtx;,之后在调用connect的时候会调用AbstractChannelHandlerContext的connect方法,最终调用的是Unsafe的connect方法

(3)AbstractNIOChannel中被赋值的属性如下。

      ch:被赋值为Java原生SocketChannel,即NioSocketChannel的newSocket()方法返回的Java NIO SocketChannel。

      readInterestOp:被赋值为SelectionKey.OP_READ。

      ch:被配置为非阻塞,即调用ch.configureBlocking(false)方法。

(4)NioSocketChannel中被赋值的属性:config=new NioSocketChannelConfig(this,socket.socket())。

  • EventLoop的初始化

MultithreadEventLoopGroup作为NioEventLoopGroup父类,基本操作都在MultithreadEventLoopGroup里完成,维护了线程池,大小如果不设置的话默认是2Ncpu,即CPU核数×2

EventLoopGroup的初始化过程。

(1)EventLoopGroup(其实是MultithreadEventExecutorGroup)内部维护一个类型为EventExecutor的children数组,其大小是nThreads,这样就构成了一个线程池。

(2)我们在实例化NioEventLoopGroup时,如果指定线程池大小,则nThreads就是指定的值,反之是CPU核数×2。

(3)在MultithreadEventExecutorGroup中调用newChild()象方法来初始化children数组。

(4)newChild()方法是在NioEventLoopGroup中实现的,它返回一个NioEventLoop实例。

(5)初始化NioEventLoop对象并给属性赋值,具体赋值的属性如下。

      ● provider:就是在NioEventLoopGroup构造器中,调用SelectorProvider.provider()方法获取的SelectorProvider对象。

      ● selector:就是在NioEventLoop构造器中,调用provider.openSelector()方法获取的Selector对象,这里边挺有意思的,利用了反射原理,重新设置了sun.nio.ch.SelectorImp的成员变量selectedKeys,过程如下:

第一步:反射获取字节码文件
Class.forName("sun.nio.ch.SelectorImpl", false,PlatformDependent.getSystemClassLoader());
第二部:获取字段
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
第三部:将包装SelectorImpl的类selectedKeys字段设置成openSelector()方法里边新定义的final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
selectedKeysField.set(unwrappedSelector, selectedKeySet);
第四部:将selectedKeySet赋值给NioEventLoop的成员变量selectedKeys,这样每次使用代理类SelectedSelectionKeySetSelector调用selectedKeys的时候,获取到的值会自动注入到NioEventLoop的selectedKeySet里边
第五部:建立代理包装类
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)

至此,所要处理的key信息全部存储到了Netty的SelectedSelectionKeySet里,之后调用NioEventLoop的processSelectedKey方法进行处理读、写等过程
题外话:线程池数量的选择上的一般规律:

Nthreads=Ncpu*Ucpu*(1+w/c),其中

Ncpu=CPU核心数

Ucpu=cpu使用率,0~1

W/C=等待时间与计算时间的比率

Nthreads=Ncpu*(1+w/c)

IO密集型:一般情况下,如果存在IO,那么肯定w/c>1(阻塞耗时一般都是计算耗时的很多倍),但是需要考虑系统内存有限(每开启一个线程都需要内存空间),这里需要上服务器测试具体多少个线程数适合(CPU占比、线程数、总耗时、内存消耗)。如果不想去测试,保守点取1即,Nthreads=Ncpu*(1+1)=2Ncpu。这样设置一般都OK。

计算密集型:假设没有等待w=0,则W/C=0. Nthreads=Ncpu。

至此结论就是:

IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)

计算密集型=Ncpu(常出现于线程中:复杂算法)

java中:Ncpu=Runtime.getRuntime().availableProcessors()

对于chooser的选择,使用的判断法,如果nThreads是2的平方,则使用PowerOfTwoEventExecutorChooser,否则使用GenericEventExecutorChooser。这里有趣的是判断2的倍数的方法,因为2倍数的相反数的反码的补码和源码相同,所以val & -val == val 就会证明了val是否是2的倍数

 public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }
 private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

还有个有趣的事情就是chooser的两种方式的原因是,netty对next方法的优化,比如如下代码中的,AtomicInteger自增长取余的过程,如果是2的倍数,使用&会比%效率更高,因为位运算是直接在内存中进行,避免了10进制转成2进制到内存中进行计算,然后再把结果转换成10进制的过程。这一点对于很通用,比如hashmap的大小建议为2的n次方的原因,有比如负载均衡轮询的自增长取余过程,等等,当然还有个有趣的是,int无线增值的循环往复0-->2^31-->-2^31--0,因为计算机使用补码,可以将符号位和其它位统一处理。

 public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
 public EventExecutor next() {
            return executors[Math.abs(idx.getAndIncrement() % executors.length)];
        }

 

  • 将Channel注册到Selector

     Channel会在Bootstrap的connect的initAndRegister()中进行初始化,并且这个方法还会将初始化好的Channe注册到NioEventLoop的Selector中。接下来我们分析一下Channel注册的过程。

Channel的注册过程,具体如下。

(1)在AbstractBootstrap的initAndRegister()方法中,通过group().register(channel)调用MultithreadEventLoopGroup的register()方法。

(2)在MultithreadEventLoopGroup的register()方法中,调用next()方法获取一个可用的SingleThreadEventLoop,然后调用它的register()方法。

  (3)在SingleThreadEventLoop的register()方法中,调用channel.unsafe().register(this,promise)方法获取Channel的unsafe()底层操作对象,然后调用Unsafe的register()方法。

(4)在AbstractUnsafe的register()方法中,调用register0()方法注册Channel对象。

(5)在AbstractUnsafe的register0()方法中,调用AbstractNioChannel的doRegister()方法。

  (6)AbstractNioChannel的doRegister()方法通过javaChannel().register(eventLoop().selector,0,this)将Channel对应的Java NIO的SocketChannel注册到一个eventLoop的Selector中,并且将当前Channel作为Attachment与SocketChannel关联。

 

  • connect过程如下图所示

 

总结:

NioEventLoopGroup 实际上就是个线程池,一个 EventLoopGroup 包含一个或者多个 EventLoop;
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
一个 Channel 在它的生命周期内只注册于一个 EventLoop;
每一个 EventLoop 负责处理一个或多个 Channel; 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Netty4简单认知 的相关文章

  • C 中逐个字符读取文件

    我正在用 C 语言编写 BF 解释器 但在读取文件时遇到了问题 我以前用过scanf为了读取第一个字符串 但是你的 BF 代码中不能有空格或注释 现在这就是我所拥有的 char readFile char fileName FILE fil
  • ObjectOutputStream 方法:writeBytes(String str) 与 writeUTF(String s);

    两者之间的主要区别是什么 它们仍然都是用于编写字符串 public void writeUTF String str throws IOException Primitive data write of this String in mod
  • 使用 System.IO.File 辅助方法的“顺序”文件 I/O 安全吗?

    我刚刚看到这个问题 在 C 中的 File 类上使用静态方法安全吗 https stackoverflow com q 32413634 1207195 总结一下OP有一个IOException因为该 ASP NET 代码片段中正在使用文件
  • 输出到另一个窗口

    有没有办法直接在 VB NET 中打开窗体并在第二个监视器中最大化 也就是说 如果显示两个监视器 默认情况下第二个窗口中的表单加载会最大化吗 假设一个程序是用两种形式编写的 并且一台计算机连接了两个显示器 我希望 FormA 默认显示在 M
  • 如何在 Perl 6 中追加到文件?

    我正在尝试这个和其他一些事情 但它每次都会截断文件 my file primes txt sub MAIN Int D low Int D high where gt low unless my fh open file w append
  • BufferedReader会将整个文件加载到内存中吗?

    class LogReader public void readLogFile String path BufferedReader br new BufferedReader new FileReader path String curr
  • Java:BufferedReader 的 readLine 方法的效率和可能的替代方案

    我们正在努力减少延迟并提高用 Java 编写的进程的性能 该进程通过 readLine 方法从套接字消费数据 xml 字符串 缓冲读取器 http java sun com javase 6 docs api java io Buffere
  • Java 中是否有与 StringWriter 等效但内部带有 StringBuilder 的东西?

    我注意到 StringWriter 在内部使用 StringBuffer 但是 如果您不需要同步开销 是否有与 StringWriter 等效的内部使用 StringBuilder 的方法 如果你恰好使用 Apache Commons IO
  • 如何在 C# 中读取文本文件并将数据添加到 int 数组中?

    我正在尝试读取一个文本文件 其中包含以逗号分隔的数字 当我阅读时使用File Readline 我把它拿到string 我需要将其转换为 int 数组 但它给出了错误 文本文件的内容 146429 143689 144380 141523
  • 没有 fflush(stdout) 则输出不打印

    我不明白为什么有时我需要使用fflush 有时不是 我的程序目前出现段错误 我正在使用 print 语句对其进行调试 当程序出现段错误时 stdout不自动刷新缓冲区 我不明白为什么有时需要使用 fflush 而有时需要使用 不是 有时 s
  • 使用 Node.js 就地流式传输和转换文件

    我想做这样的事情 var fs require fs var through require through var file path to file json var input fs createReadStream file utf
  • 按广度优先顺序列出目录所有内容导致效率低下

    我编写了一个 Haskell 模块来按广度优先顺序列出目录的所有内容 下面是源代码 module DirElements dirElem where import System Directory getDirectoryContents
  • 关闭/清理“混合”文件描述符/套接字

    当我使用accept 创建一个套接字并使用fdopen 从中创建一个文件时 我需要做什么来清理所有内容 我是否需要对 FILE 执行 fclose 对套接字执行 shutdown 和 close 还是只需要 shutdown 和 或 clo
  • 如何检查并关闭Excel文件是否已在Java中打开[重复]

    这个问题在这里已经有答案了 可能的重复 Java 检查文件是否已打开 https stackoverflow com questions 1390592 java check if file is already open 我正在制作一个
  • 更新写入 java 文本文件的对象

    将 Java 对象或列表写入文本文件是可以的 但我想知道如何更新或重写以前写入的对象而不再次写入对象 例如 假设有一个 java util List 有一组对象 然后将该列表写入文本文件 然后稍后该文件将被再次读取并从列表中获取所有对象 然
  • kqueue() 和 O_NONBLOCK

    如果您使用 kqueue 是否应该在文件描述符上设置 O NONBLOCK 换句话说 无论 O NONBLOCK 是否设置 kqueue 是否保证就绪文件描述符上的下一个 I O 操作不会阻塞 如果您使用 kqueue 是否应该在文件描述符
  • Haskell 中的 print 是纯函数吗?

    Is print在 Haskell 中是纯函数 为什么或者为什么不 我认为不是 因为它并不总是返回与纯函数应返回的值相同的值 类型的值IO Int并不是真正的Int 它更像是一张纸 上面写着 嘿 Haskell 运行时 请生成一个Int如此
  • 修饰符 async 对此项目无效

    这似乎并不是数百个具有相同错误的其他问题的重复 我把它们都看过了 发现它们是无关的 我正在制作一个小笔记应用程序 并尝试从目录中读取文件 按照 MSDN 示例 我有以下代码 但它给了我一个错误 错误 1 修饰符 async 对此无效 项目
  • 我可以用 HTML5/JS 编写文件吗?

    我想知道是否有什么方法可以从 HTML5 JS 写入文件 在浏览器中 假设您的最终目标是让用户将您的文件保存在他们能找到的地方 例如右键单击链接并选择 另存为 时 这些 API 的浏览器覆盖范围还不够广泛 这可能是由于出于安全考虑 然而 无
  • Python 读取未格式化的直接访问 Fortran 90 给出不正确的输出

    这是数据的写入方式 它是一个二维浮点矩阵 我不确定大小 open unit 51 file rmsd nn output form unformatted access direct status replace recl Npoints

随机推荐

  • 图像质量评价

    深度学习中 图片的质量往往决定了结果如何 这里抄了几种方法 1 简介 图像算法评估 定性 主观 观察 定量 客观 特征值 算法时间 定性 主要是观察 分析 定量 主要是各参数指标 又分为 参考质量 非参考质量 参考质量 处理后的图和原图之间
  • 国内名校还是国外读书

    cloudiris Iris Mon Jul 4 14 07 23 2011 年少也曾轻狂过 回头望 世事无常 伤心处 英语硬伤 个人很羡慕那些能坚定出国这一条路的人 我承认自己太软弱 总是被身边的事情羁绊 不够坚强 不过现在看来 清华这个
  • SQL Server中bcp命令的用法以及数据批量导入导出

    1 bcp命令参数解析 bcp命令有许多参数 下面给出bcp命令参数的简要解析 用法 bcp dbtable query in out queryout format 数据文件 m 最大错误数 f 格式化文件 e 错误文件 F 首行 L 末
  • Oracle删除重复数据的几种方式

    包含空字段的数据去重 DELETE FROM TABLE NAME WHERE ROWID NOT IN SELECT MIN ROWID FROM TABLE NAME GROUP BY COLUMN1 COLUMN2 不包含空字段的数据
  • 手把手教你如何安装Pycharm

    今天给大家分享如何在本机上下载和安装Pycharm 具体的教程如下 1 首先去Pycharm官网 或者直接输入网址 http www jetbrains com pycharm download section windows 下载PyCh
  • Spring AOP 源码解析

    AOP中的几个概念 Advisor 和 Advice Advice 我们通常都会把他翻译为通知 其实很不好理解 其实他还有另外一个意思 就是 建议 我觉得把Advice理解为 建议 会更好 比如 我们已经完成了一个功能 这时客户跟我们说 我
  • Spark组件:RDD、DataFrame和DataSet介绍、场景与比较

    1 rdd dataframe dataset在哪个版本被引入 2 什么情况下使用rdd dataframe dataset 3 它们有什么不同 spark生态系统中 Spark Core 包括各种Spark的各种核心组件 它们能够对内存和
  • 数字后端知识点扫盲——芯片harden block的划分

    后端在floorplan阶段 如何摆放macro是一个很重要的问题 如果采用层次化设计 对于每一个block来说都需要在block内部把锁分配的macro摆好 如果某一个block里的macro很多 而且又很大 std cell再多一些 这
  • Lattice 开发工具Diamond 相关版本下载地址

    百度网盘 https wenku baidu com view 21b98975192e45361066f5f3 html 官网下载 http www latticesemi com Support SoftwareArchive aspx
  • 'DataFrame' object has no attribute 'dtype'

    这个错误消息的意思是 在你的代码中 你试图访问一个Pandas DataFrame对象的 dtype 属性 但该对象并没有这个属性 在Pandas中 DataFrame没有 dtype 属性 你可以使用 dtypes 属性来获取DataFr
  • 在vs2019配置MySQL环境,不需要每次新建一个项目重新配置的方法(图文详解)

    目录 问题 解决方法 问题 上一期讲到怎么在vs2019去配置mysql的编译环境vs2019 c c 配置MySQL数据库的环境 图文详解 守约斯维奇的博客 CSDN博客 这里我们会觉得当建立一个新项目的时候去配置mysql的编译环境非常
  • Qt D-Bus

    介绍 D Bus是为Linux系统开发的进程间通信 IPC 和远程过程调用 RPC 机制 使用统一的通信协议来代替现有的各种IPC解决方案 它允许系统级进程 如 打印机和硬件驱动服务 和普通用户进程进行通信 它使用一个快速的二进制消息传递协
  • 分布式理论-拜占庭将军(译)

    作者 LESLIE LAMPORT ROBERT SHOSTAK and MARSHALL 1982 译者 phylips bmy 出处 http duanple blog 163 com blog static 7097176720112
  • springBoot 拦截器

    声明 代码是JavaEE开发的颠覆者 Spring Boot实战代码中的 我买了这书 并练习 public class DemoInterceptor extends HandlerInterceptorAdapter 1 Override
  • 笔录getResource() 与 getClassLoader().getResource()

    结论 1 Class getResource String path path路径的前缀不是 表示从此类所在的包下取资源文件 path路径的前缀是 则是从ClassPath根下获取资源文件 Class getResource和Class g
  • python seleium b站 自动投币脚本

    主要是给我的投币器做个铺垫 果然软件还是太容易了 难在硬件和外壳好吗 1 把edge的调试端口打开 添加以下参数 C Program Files x86 Microsoft Edge Application msedge exe remot
  • 在express项目里配置ejs模板引擎

    方法1 修改app js view engine setup app set views path join dirname views app set view engine ejs 在views中创建ejs模板文件 方法2 修改app
  • 老年人晕倒的几种原因

    晕倒是一种突发性 短暂性 一过性的意识丧失而昏倒 突然性的晕倒 跟大脑的神经有分不开关系 癫痫 脑供血不足 心脑血管疾病都是引起头晕倒的原因 大脑血液上不来 脑血液突然停止 就会产生放电波头晕的症状 患者会在一时间出现晕倒 很容易引起脑震荡
  • 在conda虚拟环境中的PyQt配置以及相关Pycharm设置

    文章目录 在conda虚拟环境中的PyQt配置 背景环境介绍 PyQt依赖包及PyQt tools的下载 Pycharm进行PyQt的相关配置 结语 在conda虚拟环境中的PyQt配置 作者 下龙湾 背景环境介绍 anaconda安装文件
  • Netty4简单认知

    Channel简介 在Netty中 Channel相当于一个Socket的抽象 它为用户提供了关于Socket状态 是连接还是断开 及对Socket的读 写等操作 每当Netty建立了一个连接 都创建一个与其对应的Channel实例 Cha