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.mzph.cn/news/494157.shtml

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

相关文章

黎曼猜想被证明了?“他的证明甚至不能算是个错误”!阿蒂亚爵士的证明受到同行质疑...

作者&#xff1a;许琦敏 金婉霞编辑&#xff1a;金婉霞责任编辑&#xff1a;李雪林来源&#xff1a;解剖者摘要&#xff1a;德国柏林时间9月24日上午9点45分&#xff0c;菲尔兹奖与阿贝尔奖双料得主、英国皇家学会院士迈克尔阿蒂亚爵士在德国海德堡举行的海德堡奖诺贝尔奖获得者…

Android自定义View

1.View是什么&#xff1f; View是屏幕上的一块矩形区域&#xff0c;它负责用来显示一个区域&#xff0c;并且响应这个区域内的事件。可以说&#xff0c;手机屏幕上的任意一部分看的见得地方都是View&#xff0c;它很常见&#xff0c;比如 TextView 、ImageView 、Button以及Li…

【IT笔试面试题整理】判断链表是否存在环路,并找出回路起点

【试题描述】定义一个函数&#xff0c;输入一个链表&#xff0c;判断链表是否存在环路&#xff0c;并找出回路起点 Circular linked list: A (corrupt) linked list in which a node’s next pointer points to an earlier node, so as to make a loop in the linked listEXAMP…

腾讯机器人实验室首曝光 攻坚“通用人工智能”

来源&#xff1a;新浪科技摘要&#xff1a;与当初的“互联网”一样&#xff0c;“AI”正成为各行各业的标配。在近日召开的2018 世界人工智能大会上&#xff0c;腾讯董事会主席兼首席执行官马化腾提出&#xff0c;人工智能技术是一场跨国、跨学科的科学探索工程&#xff0c;对于…

Android之canvas详解

首先说一下canvas类&#xff1a; Class Overview The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e…

下一代动力电池深度报告,三大技术路线谁能笑到最后?【附下载】| 智东西内参...

来源&#xff1a;智东西编辑&#xff1a;智东西内参摘要&#xff1a;随着全球电动车浪潮席卷&#xff0c;关于固态电池的新闻越来越多。从 Fisker 宣称开发充电 1 分钟行驶 500 公里的固态电池&#xff0c;到宝马已与 SolidPower 进行合作开发下一代电动车用固态电池&#xff0…

Android之shape属性详解

有时候 &#xff0c;为了满足一些需求&#xff0c;我们要用到 shape 去定义 一些背景&#xff0c;shape 的用法 跟图片一样 &#xff0c;可以给View设置 Android:background”drawable/shape”, 定义的shape 文件&#xff0c;放在 res/shape 目录下 通常我们可以用shape 做 bu…

S3C6410移植u-boot-2010.3(2)基本的启动信息修改

1、启动模块修改 进入/cpu/arm1176/目录&#xff0c;修改start.S文件 首先找到需要修改的CONFIG_NAND_SPL汇编原码&#xff0c;修改如下&#xff1a; #ifndef CONFIG_NAND_SPL /** flush v4 I/D caches*/ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ mcr p1…

[修订版]”大脑“爆发背后是50年互联网架构重大变革

前言&#xff1a;面对即将到来的2019年&#xff0c;互联网诞生50年将是诸多纪念活动中重要的一个&#xff0c;经过50年的发展&#xff0c;互联网究竟发生什么重要的变化&#xff0c;通过这篇文章试图进行一次总结&#xff0c;也提前向互联网50年致敬。作者&#xff1a;刘锋 互…

智慧城市建设:科技创业的下一个浪潮

来源&#xff1a;资本实验室随着全球城市化进程的加速&#xff0c;越来越多的人涌进城市&#xff0c;这为城市建设带来了一系列的挑战&#xff1a;一方面&#xff0c;城市需要面对大量的越来越老化的基础设施&#xff1b;另一方面&#xff0c;需要为新涌入的城市居民提供新的&a…

Android之ViewDragHelper

在自定义ViewGroup中&#xff0c;很多效果都包含用户手指去拖动其内部的某个View(eg:侧滑菜单等)&#xff0c;针对具体的需要去写好onInterceptTouchEvent和onTouchEvent这两个方法是一件很不容易的事&#xff0c;需要自己去处理&#xff1a;多手指的处理、加速度检测等等。 好…

DARPA人工智能技术研究情况一览

来源&#xff1a;一体化指挥调度国家工程实验室、高端装备发展研究中心摘要&#xff1a;20世纪60年代初&#xff0c;DARPA&#xff08;当时为ARPA&#xff09;开始介入自主技术研究&#xff0c;并很快成为该领域的主要研究机构。DARPA意识到&#xff0c;人工智能可以满足大量的…

深入“肠-脑”神经高速通道,揭开“第六感觉”面纱

来源&#xff1a;中国生物技术网直觉是什么&#xff1f;通常被描述为超感官的第六感觉&#xff0c;它在英文里直译就是肠道感觉。肠道作为“第二大脑”的事实已经家喻户晓了。如果你曾在重要的演讲前感到心慌恶心&#xff0c;或者在一顿大餐后感到头昏眼花&#xff0c;那就是肠…

Andoird自定义ViewGroup实现竖向引导界面

一般进入APP都有欢迎界面&#xff0c;基本都是水平滚动的&#xff0c;今天和大家分享一个垂直滚动的例子。 先来看看效果把&#xff1a; 首先是布局文件&#xff1a; <com.example.verticallinearlayout.VerticalLinearLayout xmlns:android"http://schemas.android.…

科技|全球首款飞行汽车开始量产!下月开始预售,2023年后或可实现一键打“飞车”...

来源&#xff1a; 世界科技创新论坛飞机与汽车结合的产物真的要来了。在2018全球未来出行大会上&#xff0c;吉利副总裁杨学良透露&#xff0c;由吉利控股的全资子公司、全球首家飞行汽车公司美国太力飞行汽车公司推出的“全球首款量产飞行汽车”——Transition&#xff0c;将于…

Android手势锁实现

最终效果如下 整体思路 a、自定义了一个RelativeLayout(GestureLockViewGroup)在里面会根据传入的每行的个数&#xff0c;生成多个GestureLockView&#xff08;就是上面一个个小圈圈&#xff09;&#xff0c;然后会自动进行布局&#xff0c;里面的宽度&#xff0c;间距&#x…

智能连接:5G与人工智能、物联网等技术的超级融合

来源&#xff1a;资本实验室随着新技术的成熟&#xff0c;新型的、先进的应用将来自5G、人工智能&#xff08;AI&#xff09;和物联网&#xff08;IoT&#xff09;的融合。这种融合将创造出一个智能连接的世界&#xff0c;对所有个人、行业、社会和经济产生积极影响。从现在到2…

一个绚丽的loading动效分析与实现!

最终效果如下 从效果上看&#xff0c;我们需要考虑以下几个问题&#xff1a; 1.叶子的随机产生&#xff1b; 2.叶子随着一条正余弦曲线移动&#xff1b; 3.叶子在移动的时候旋转&#xff0c;旋转方向随机&#xff0c;正时针或逆时针&#xff1b; 4.叶子遇到进度条&#xff…

20岁的谷歌,和它“最成功”的大败笔

来源&#xff1a;大数据文摘编译&#xff1a;张驰、JIN、涂世文、钱天培谷歌20岁了&#xff01;20年中&#xff0c;谷歌打造了无数或成功或流产的产品&#xff0c;其中&#xff0c;这一名为“谷歌光纤”计划的失败或许是它最“成功”的“大败笔”。2010年&#xff0c;谷歌宣布了…

自定义viewgroup实现ArcMenu

最终效果如下 实现思路 通过效果图&#xff0c;会有几个问题&#xff1a; a、动画效果如何实现 可以看出动画是从顶点外外发射的&#xff0c;可能有人说&#xff0c;那还不简单&#xff0c;默认元素都在定点位置&#xff0c;然后TraslateAnimation就好了&#xff1b;这样忽略…