Netty-4-网络编程模式

我们经常听到各种各样的概念——阻塞、非阻塞、同步、异步,这些概念都与我们采用的网络编程模式有关。

例如,如果采用BIO网络编程模式,那么程序就具有阻塞、同步等特质。

诸如此类,不同的网络编程模式具有不同的特点,这些网络编程模式就相当于我们的网络编程套路。

因此,了解并掌握网络编程模式是学习Netty和使用Netty进行网络编程的必经之路。

下面我们学习网络编程模式以及Netty如何对它们提供支持。

网络编程的3种模式

当我们去饭店吃饭时,会经常遇到以下三种模式。

  • 排队打饭模式。这种模式主要出现在食堂等场所。人们在窗口前排队,饭菜打好后才走,若饭菜没有打好,人们一般是不会主动离开的。
  • 点餐等待被叫模式。在这种模式下,我们会收到点餐号,饭店备好饭菜后就呼叫点餐号,我们需要自行去取。
  • 包厢模式。这是我们最喜欢的模式,点餐后什么都不用管,坐在那里等着饭菜被服务员端上桌即可。

为了方便解释,我们可以将就餐模式与服务器应用做类比。

例如,把饭店比作服务器,而把饭菜比作数据。

这样的话,饭菜好了就相当于数据就绪,而端菜行为则可以当作读取数据。

通过进行类比,我们发现就餐的3种模式其实正好对应经典的3种网络编程I/O模式。
在这里插入图片描述
阻塞与非阻塞之间的区别在于要不要一直等,直到饭菜做好。

换言之,对于阻塞而言,在数据没有传输过来之前,会阻塞等待,直到数据到来;写的过程也类似,当缓冲区满时,写操作也会被阻塞,直到缓冲区"可写”。

但是,对于非阻塞而言,遇到这些情况则不做任何停留, 直接返回。

同步和异步之间的区别在于数据就绪后谁来读,类似于“饭菜好了谁来端”的问题。

数据就绪后,如果需要应用程序自行读取,就是同步过程;数据就绪后,如果由系统直接读取并回调给程序,就是异步过程。

在区分完以上两组概念后,我们可以做下对应:BIO是阻塞同步方式;NIO是非阻塞同步方式;AIO是非阻塞异步方式。

网络编程模式的选择要点

从表面上看,我们一般倾向于使用新的模式。

例如,我们青睐的顺序可能是AIO-NTO-BIO但是实际上,看似明显的优先顺序并不是什么"黄金准则”。

我们需要结合更多的因素来决定如何做出选择。

服务的连接数

假设应用程序的连接数有限,比如只有一两个连接,我们就无法预见NIO模式的性能肯定比传统程序的BIO模式好。

不过,可以预见的是,NIO模式的代码实现复杂度肯定高于BIO模式。

因此,在选择I/O模式时,我们需要了解应用程序到底能够支持多少个连接。

服务将要部署到的平台

对于Windows平台,我们一般都会优先选择AIO模式。
但是对于Linux平台,情况将可能有所不同,毕竟Linux平台对AIO模式的支持还不够成熟。

另外,有关NIO和AIO模式的一些测试表明,其实在Linux平台上,NIO和AIO模式的代码实现并无太大性能差别。

因此,对于大多数使用Linux作为服务器的应用平台而言,NIO模式或许才是更好的选择。

当前已有的架构和可用的实现

如果当前项目一直使用某种模式,那么我们一般很少有勇气去直接变革和采用新的模式,而是修修补补并沿用旧的模式。

此外,即使对于全新的项目,也不一定会选择我们想用的模式。例如,假设要选择AIO模式,但我们的技术栈是基于Netty的,那么AIO模式用不了。

综上所述,不同网络编程模式有自己适用的场景,我们不能仅仅依据推出时间的前后就直接做出决策。

换言之,具体问题具体分析、具体场景具体选择才是永恒之道。

Netty对网络编程模式的支持

在这里插入图片描述

Netty目前只推崇NIO模式。为什么不推荐另外两种模式呢?

Netty为什么不推荐BIO/OIO模式?

在连接数较多的情况下,BIO/OIO模式的阻塞特性就意味着耗资源、效率低。具体而言, 阻塞就意味着等待,等待就会占用线程。
考虑一下,在连接数比较多的情况下,如果每个连接上的请求又都在等待,占用的线程将会非常多,资源耗费就太大了。
不仅如此,一个进程对所能创建的线程数量也是有约束的,这一点无法克服。

其次,Netty为什么废弃AIO模式?原因主要有三点。

  1. Windows平台上的实现虽然已经非常成熟,但是Windows平台本身很少用作服务器。
  2. Linux平台经常用作服务器,但是Linux平台上AIO模式的实现还不够成熟。
  3. Linux平台上AIO模式的实现相比NIO模式而言稍复杂一些,但性能提升并不明显。

由上可知,对于Netty而言,NIO是Netty推崇的核心I/O模式。但是,有必要补充说明的是,对于NIO模式的实现方式,Netty支持的不止一种。

在这里插入图片描述
Netty的通用NIO模式的实现方式在Linux平台上使用的也是EpolL那么为什么此处还需要一套专有的Epoll相关实现呢?
“重新造轮子”无非有两个原因,自己的轮子更好、更强,这个道理同样适用于此。

具体原因包括以下两个方面。

  • Epoll相关实现能够暴露更多可控的参数:JDK的很多参数都不可调。例如,JDK的NIO模式在Linux平台上默认是水平触发且不可修改的;但对于Netty而言,水平触发和边缘触发都支持,并且可以切换(默认是边缘触发)。

  • 垃圾回收更少,性能更好:这是Netty开发者自己给出的理由。不过,我们有理由相信Netty确实做到了,否则没有必要"重新造轮子"。

Netty对网络编程模式的实现

        // 创建一个ServerBootstrap实例ServerBootstrap serverBootstrap = new ServerBootstrap();// 设置服务器通道类型为OioServerSocketChannelserverBootstrap.channel(OioServerSocketChannel.class);// 创建一个OioEventLoopGroup实例OioEventLoopGroup eventLoopGroup = new OioEventLoopGroup();// 设置服务器线程组为创建的OioEventLoopGroup实例serverBootstrap.group(eventLoopGroup);

OIO模式的使用主要涉及OioServerSocketChannel和OioEventLoopGroup这两个关键类。

ServerSocketChannel 的创建

当执行 serverBootstrap.channel (OioServerSocketChannel.class)时,实际上执行的是如下方法:

//AbstractBootstrap.java/*** 设置通道的类型为指定的通道类* * @param channelClass 通道类* @return 通道配置对象*/public B channel(Class<? extends C> channelClass) {return channelFactory(new ReflectiveChannelFactory<C>(ObjectUtil.checkNotNull(channelClass, "channelClass")));}

上述代码创建了 ReflectiveChannelFactory 来负责创建 OioServerSocketChannelo 顾名思义,ReflectiveChannelFactory使用“反射"方式来完成上述工作。

//ReflectiveChannelFactory.javaprivate final Constructor<? extends T> constructor;  // 构造器,用于创建指定类型的对象public ReflectiveChannelFactory(Class<? extends T> clazz) {  // 构造函数,用于创建ReflectiveChannelFactory对象ObjectUtil.checkNotNull(clazz, "clazz");  // 检查clazz是否为nulltry {this.constructor = clazz.getConstructor();  // 获取指定类的无参构造函数} catch (NoSuchMethodException e) {throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +" does not have a public non-arg constructor", e);  // 如果指定类没有无参公共构造函数,则抛出异常}}@Overridepublic T newChannel() {  // 创建一个指定类型的对象try {return constructor.newInstance();  // 使用构造器创建对象} catch (Throwable t) {throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);  // 如果创建对象过程中发生异常,则抛出ChannelException异常}}
EventLoopGroup 的功能

EventLoopGroup 负责给每个通道分配 EventLoop0 例如,OioEventLoopGroup 负责给 BIOChannel分配ThreadPerChannelEventLoop (注意,这里的命名方式和其他的EventLoop不同)。

//ThreadPerChannelEventLoop.java@Overrideprotected void run() {for (;;) {// 从任务队列中取出一个任务Runnable task = takeTask();if (task != null) {// 执行任务task.run();// 更新最后一次执行时间updateLastExecutionTime();}// 获取当前的通道Channel ch = this.ch;if (isShuttingDown()) {// 如果正在关闭if (ch != null) {// 关闭通道ch.unsafe().close(ch.unsafe().voidPromise());}// 如果确认关闭if (confirmShutdown()) {break;}} else {// 如果没有正在关闭if (ch != null) {// 处理注销if (!ch.isRegistered()) {// 执行所有任务runAllTasks();// 注销deregister();}}}}}

ThreadPerChannelEventLoop在本质上相当于任务的执行体,而任务本身就是执行通道上的读写操作。例如,当写数据时,写数据这一操作会被当作任务提交给SingleThreadEventExecutor (ThreadPerChannelEventLoop 的父类)的 execute 方法来执行。


//SingleThreadEventExecutor.javaprivate void execute(Runnable task, boolean immediate) {// 判断是否在事件循环中boolean inEventLoop = inEventLoop();// 添加任务到任务队列addTask(task);if (!inEventLoop) {// 启动线程startThread();// 如果已经关闭if (isShutdown()) {// 是否从任务队列中移除任务boolean reject = false;try {// 如果移除了任务,则标记为拒绝if (removeTask(task)) {reject = true;}} catch (UnsupportedOperationException e) {// 任务队列不支持移除任务,只能继续希望在任务完全终止之前能够取而代之。// 最坏情况下在任务终止时进行日志记录。}// 如果被拒绝,则进行拒绝处理if (reject) {reject();}}}// 如果不希望通过添加任务唤醒线程以及立即执行if (!addTaskWakesUp && immediate) {// 唤醒线程wakeup(inEventLoop);}}

最终执行的是OioByteStreamChannel#doWriteBytes()方法。

//OioByteStreamChannel.java@Overrideprotected void doWriteBytes(ByteBuf buf) throws Exception {// 获取输出流OutputStream os = this.os;// 如果输出流为空,则抛出未连接异常if (os == null) {throw new NotYetConnectedException();}// 将ByteBuf中的字节写入输出流中buf.readBytes(os, buf.readableBytes());}

要完成对一种I/O模式的支持,我们至少需要两个组件: SocketChannel负责完成具体的读写务;NioEventLoop负责任务的执行。当需要切换I/O模式时,直接替换掉这些实现即可。

常见疑问

水平触发和边缘触发的定义

Netty既支持水平触发也支持边缘触发。Netty确实提供了这两种触发方式的定义。

public enum EpollMode {EDGE_TRIGGERED,LEVEL_TRIGGERED;private EpollMode() {}
}

那么什么是水平触发和边缘触发?在理论层次上进行解析。

水平触发

当被监控的文件描述符上有可读写事件时,通知用户去读写。如果用户一次没有读写完数据,就一直通知用户。

在用户确实不怎么关心这个文件描述符的情况下,频繁通知用户会导致用户真正关心的那些文件描述符的处理效率降低。

比如:点餐后,饭菜做好了(数据就绪),服务员端上来问你吃不吃(读写数据)。 不管你吃溢还是吃不完,服务员总是过来反复提醒你吃饭。

边缘触发

当被监控的文件描述符上有可读写事件时,通知用户去读写,但只通知一次,这就需要用户一次性把数据读写完。

如果用户没有一次性读写完数据,那就需要等待下一次新的数据到来时,才能读写上次未读写完的数据。

比如:服务员端来饭菜后,你没有一次性吃完,等你想吃剩下的饭菜时,就必须再次点餐才行。

Epoll既支持水平触发也支持边缘触发,那么该如何选择呢?如果选择水平触发,就要注意效率和资源利用率;而如果选择边缘触发,就要注意自身是否能一次性完成数据的读写。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/224112.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化

2022年亚马逊云科技re:Invent盛会于近日在拉斯维加斯成功召开&#xff0c;吸引了众多业界精英和创新者。亚马逊云科技边缘服务副总裁Jan Hofmeyr在演讲中分享了关于亚马逊云科技海外服务器边缘计算的最新发展和创新成果&#xff0c;引发与会者热烈关注。 re:Invent的核心主题是…

VMware虚拟机的安装配置

目录 一. VMware虚拟机的安装 二. VMware配置虚拟机 三. VMware安装windows server 2012 一. VMware虚拟机的安装 1. 双击安装&#xff0c;点击下一步 2. 勾选接受许可&#xff0c;点击下一步 3. 选择安装位置&#xff0c;点击下一步 4. 用户体验设置&#xff08;可选&#…

2024年PMP考试新考纲-PMBOK第七版-项目管理原则真题解析(续3)

马上就要进入2024年了&#xff0c;要参加2024年PMP一季度考试的小伙伴可以准备起来了。2024年的PMP考试将继续采用新考试大纲&#xff0c;考试内容包括PMBOK第六版、PMBOK第七版和敏捷实践指南&#xff0c;而且敏捷&#xff08;或者叫混合&#xff09;的项目环境将占比超过50%&…

Python的基本数据类型和数据类型的转换

TOC 数据类型 类型查看 type 可以使用type内置函数查看变量所指的对象类型 a1 b1.0 c"1" d1, e[1] f{1:1} g{1}print(type(a)) print(type(b)) print(type(c)) print(type(d)) print(type(e)) print(type(f)) print(type(g))isinstance **如字面意思,isinstance()…

linux运行可执行文件,通过c语言调用java的main方法

前言&#xff1a;以前一直在做Android开发&#xff0c;在某本书上看过一句话“Android上面不只有App类的程序可以运行&#xff0c;能在linux下运行的程序&#xff0c;也可以在Android上面运行” 一.编写C语言部分代码 1.定义java.h头文件 #include <jni.h>#ifndef _JAV…

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

制作自己的 Docker 容器

软件开发最大的麻烦事之一&#xff0c;就是环境配置。用户必须保证操作系统的设置&#xff0c;各种库和组件的安装&#xff0c;只有它们都正确&#xff0c;软件才能运行。docker从根本上解决问题&#xff0c;软件安装的时候&#xff0c;把原始环境一模一样地复制过来。 以 koa-…

RHCE9学习指南 第9章 权限管理

9.1 所有者所属组 为了了解所有者和所属组的概念&#xff0c;我们先看图9-1。 图9-1 用房子来帮助理解所有者和所属组 张老板是公司老板&#xff0c;买了一套房作为员工宿舍给A部门的员工居住。张老板是房主&#xff0c;所以他对房子具有很多权限&#xff0c;A部门员工只能具…

SuperMap iServer发布的ArcGIS REST 地图服务如何通过ArcGIS API加载

作者&#xff1a;yx 文章目录 一、发布服务二、代码加载三、结果展示 一、发布服务 SuperMap iServer支持将地图发布为ArcGIS REST地图服务&#xff0c;您可以在发布服务时直接勾选ArcGIS REST地图服务&#xff0c;如下图所示&#xff1a; 也可以在已发布的地图服务中&#x…

【量化金融】证券投资学

韭菜的自我修养 第一章&#xff1a; 基本框架和概念1.1 大盘底部形成的技术条件1.2 牛市与熊市1.3 交易系统1.3.1 树懒型交易系统1.3.2 止损止损的4个技术 第二章&#xff1a;证券家族4兄弟2.1 债券&#xff08;1&#xff09;债券&#xff0c;是伟大的创新&#xff08;2&#x…

赛宁综合安全验证评估,筑牢关基网络安全屏障

在国际复杂态势和数字经济发展的驱动下&#xff0c;关键信息基础设施&#xff08;以下简称&#xff1a;关基&#xff09;的安全运营逐步走向实战化、体系化和常态化。验证评估作为安全运营的试金石&#xff0c;已成为实现动态防御、主动防御的有力手段。如何通过体系化验证评估…

Flutter 三: Dart

1 数据类型 数字(number) int double 字符串转换成 num int.parse(“1”) double.parse(“1”);double 四舍五入保留两位小数 toStringAsFixed(2) 返回值为stringdouble 直接舍弃小数点后几位的数据 可使用字符串截取的方式 字符串(string) 单引号 双引号 三引号三引号 可以输…

云计算与大数据之间的羁绊(期末不挂科版):云计算 | 大数据 | Hadoop | HDFS | MapReduce | Hive | Spark

文章目录 前言&#xff1a;一、云计算1.1 云计算的基本思想1.2 云计算概述——什么是云计算&#xff1f;1.3 云计算的基本特征1.4 云计算的部署模式1.5 云服务1.6 云计算的关键技术——虚拟化技术1.6.1 虚拟化的好处1.6.2 虚拟化技术的应用——12306使用阿里云避免了高峰期的崩…

Unity 人物方向旋转详细讲解

Unity 人物方向旋转详细讲解 人物的旋转有很多种一、在介绍之前我们需要理解Unity的向量也就是Vector3二、下面我们创建两个小球f1,f2左边的为f2 右边的为f1 三、我们将小球坐标用白色直线画出来&#xff0c;两个小球之间用黑色线画出来&#xff0c;两个小球的向量用黄线表示接…

关于JVM的垃圾回收GC的一些记录

目录 一、JVM内存区域划分 二、从一个基本问题开始引入垃圾回收 三、GC作用的区域 三、如何确定一个对象是否可以被当成垃圾进行回收 &#xff08;1&#xff09;引用计数法 &#xff08;2&#xff09;可达性分析算法 &#xff08;3&#xff09;引用的类型 &#xff08;3…

(Matlab)基于CNN-LSTM的多维时序回归预测(卷积神经网络-长短期记忆网络)

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、代码实际运行结果展示&#xff1a; 三、部分代码展示&#xff1a; 四、本文完整代码数据下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码…

【Kubernetes】控制器Statefulset

Statefulset控制器 一、概念二、Statefulset资源清单文件编写技巧2.1、查看定义Statefulset资源需要的字段2.2、查看statefulset.spec字段如何定义2.3、查看statefulset的spec.template字段如何定义 三、Statefulset使用案例&#xff1a;部署web站点3.1、编写一个Statefulset资…

【四】记一次关于架构设计从0到1的讨论

记一次关于架构设计从0到1的讨论 简介&#xff1a; 在一次面试中和面试官讨论起来架构设计这个话题&#xff0c;一聊就不知不觉一个小时了&#xff0c;感觉意犹未尽。现在回想起来感觉挺有意思的&#xff0c;古人说独学而无友则孤陋而寡闻&#xff0c;的确是这样的&#xff0c…

【并发编程篇】读锁readLock()和写锁writeLock()

文章目录 &#x1f6f8;情景引入⭐解决问题 readLock()和writeLock()都是ReadWriteLock接口中定义的方法&#xff0c;用于获取读锁和写锁。 readLock()方法返回一个读锁&#xff0c;允许多个线程同时获取该锁&#xff0c;以进行并发读取操作。如果当前已有一个写锁或其他线程正…