热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Netty简介

很早以前其实就写过关于Netty的使用,最近发现在CSDN上一直有人在看很早写的Netty文章,个人感觉那时候写的很粗糙,怕影响同行的阅读质量,但是我也不知道为啥有这么多小伙伴关注
Netty 入门简介

很早以前其实就写过关于 Netty 的使用,最近发现在CSDN上一直有人在看很早写的 Netty 文章,个人感觉那时候写的很粗糙,怕影响同行的阅读质量,但是我也不知道为啥有这么多小伙伴关注Netty,所以决定重新写一些关于Netty的文章,补充以前的不足吧。

Netty能做啥

简单说就是用来处理网络编程,写一款能进行网络通信的服务端和客户端程序。

如果没有 Netty,在 Java 的世界中如何处理网络编程呢?

Java自带的工具有:java.net 包,用于处理网络通信,后面Java提供了 NIO 工具包用于提供非阻塞的通信。
与Netty同级别的第三方工具包:Mina,在设计上与Netty 有些许不同,但是核心都是提供网络通信的能力。

传统网络通信模型

说Netty之前还是先讲一下传统的网络编程是什么样子。传统的Socket编程开发步骤很简单,只需要使用Socket类创建客户端和服务端即可。但是为啥现在没有人用它了呢?主要原因是它基于同步阻塞 IO 的线程模型去做的,在当今时代完全不能满足生产需要,自然被out。

同步阻塞线程模型的问题在于一个请求必须绑定一个线程去处理,并且所有的请求都是同步操作,意味着该请求未处理完之前这个连接不会被释放,如果并发高的情况必然会导致系统压力过大。

Netty 的新线程模型

基于此,Java新增了非阻塞的IO操作包 NIO, NIO 的线程模型采用了Reactor 模式,即异步非阻塞的方式,解决了之前同步阻塞带来的问题。

NIO 的全称是 NoneBlocking IO,非阻塞 IO,区别与 BIO,BIO 的全称是 Blocking IO,阻塞 IO。那这个阻塞是什么意思呢?

  1. Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继;
  2. Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理;
  3. Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求。

服务器在处理响应的设计模式方面目前主要分为两种:线程驱动事件驱动。同步阻塞就是线程驱动的模式,最明显的例子就是 Tomcat;对于事件驱动来说,没有必要为每一个连接都创建一个线程去维护,参考观察者模式,可以设置一个事件池,用一个单线程去循环监听当前池中是否有完成的事件,如果有则取出该事件。

简单说一下 Reactor 模式是如何解决线程等待问题的:在等待IO的时候,线程可以先退出不用一直等待IO操作。但是如果不等待那么IO处理完成之后返回给谁呢?Reactor模型采用了事件驱动机制,要求线程在退出前向event loop注册回调函数,这样IO完成之后 event loop 就可以调用回调函数完成数据返回。

在Reactor中有 4 个角色,所有的数据流入的处理统一称为 Channel,就像是一个水管,Reactor 模型将每一种事件拆分为一个 event,相同类型的 event 归为一类,这一类的统一处理逻辑被称为一个 handler。那么怎么去让一个或者多个线程去监听所有的 Channel 呢? 所以就有 Selector,Selector 就像是一个管理者,你可以将多个 Channel 注册到 一个Selector 线程上,它会使用一个阻塞方法去捕获当前 Channel 上是否有事件发生,如果有则取出事件交给对应的 handler 去处理。

Netty 是建立在 NIO 之上的,并且 Netty 在 NIO 上面又提供了更多高层次 API 的封装。

为什么不用 JDK 提供的 NIO

JDK 已经给我们提供了 NIO 的包,也是使用了 Reactor 模型来实现的异步非阻塞模式,那我们为啥在日常开发中没有听到谁直接使用 NIO 来开发网络编程呢?实际上大家不使用的原因是因为它太难控制。Java NIO 类库中主要提供的功能包括:

  • 缓冲区 Buffer
  • 通道 Channel
  • 多路复用器 Selector

缓冲区 Buffer 其实就是一个对象,即所有流入或者流出的数据都在Buffer中存在。

新 IO 与老的面向流 IO 的区别在于老 IO 直接面向字节流进行处理,新 IO 是面向缓冲区进行处理,读写数据都是先读写到缓冲区中。缓冲区实质上是一个字节数组,NIO 提供了对缓冲区数据读写位置维护的操作能力。

Channel 通道,所有 Buffer内的数据都会往 Channel 上流,数据通过 Channel 留向处理逻辑,通过 Channel 将处理过的数据返回给客户端。所以 Channel 是全双工的,可以支持读写,这是它与 Stream 的区别。如果你使用Stream,读数据只能使用 InputStream 进行操作,写数据只能使用 OutputStream 进行操作。用现实世界中的事物比喻的话,传统 IO 犹如水管,水流只能沿着管道往下流; NIO 犹如一条双向公路,两个方向都可以行车。

另外也正是因为 Buffer 的引入我们才能随意的控制每次传输读多少数据,如果上次读取失败,那么应该从多少偏移量重新读取,这是传统 I/O流无法比拟的。

Selector 选择器,它是 NIO 的核心,一个 Selector 就是一个线程,NIO 允许一个 Selector 管理多个 Channel,即将 Channel 注册到 Selector 上,Selector 会去监听注册的 Channel 上是否有事件准备就绪,如果有就取出处理。

关于 NIO 的代码我就不写了,是很庞大的一堆,大家百度一下就能看到。总之基于这个思想来进行网络编程肯定是面对当今流量洪峰的最佳方式。而正好 Netty 底层基于 NIO 去做的封装,已经给你屏蔽了这一大坨操作。

网络编程还有一个问题就是跨平台性,NIO 底层是依赖系统的 IO API,不同的系统可能对 IO API 的实现也是不一样的,这里如何你使用 NIO 那么就需要考虑系统兼容性问题了。

另外还有一个问题就是 NIO 有个很著名的 bug,JDK 的 NIO 底层由 epoll 实现,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU 使用率100%。这个 bug 官方声明已经修复,事实上没有被 fix, 只是出现的概率会降低一些。

Netty 也对该 bug 进行了处理:对 Selector 的 select 操作周期进行统计,每完成一次空的 select 操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了 epoll 死循环bug。那么这个时候就重建 Selector,判断是否是其他线程发起的重建请求,若不是则将原 SocketChannel 从旧的 Selector上去除注册,重新注册到新的 Selector 上,并将原来的 Selector 关闭。

网络编程应该注意什么

既然说要学习 Netty, 它本身是基于 NIO 的封装用于网络通信,那么在编写一段用于网络通信的代码我们应该注意一些什么呢?弄清楚这些问题,我们大概就知道 Netty 都做了什么。

谈到网络就不能避免说到 OSI 7层模型 和 TCP / IP 4层模型。

Java 网络编程主要使用的是 Socket 套接字编程,基于 4层 协议的网络编程,即基于 TCP/ UDP 协议的封装。编写一个 Socket 通信都有哪些步骤呢?

  1. 创建一个 ServerSocket,监听并绑定一个端口;

  2. 一系列客户端来请求这个端口;

  3. 服务器使用 Accept,获得一个来自客户端的Socket连接对象;

  4. 启动一个新线程处理连接;

    1. 读 Socket,得到字节流,
    2. 解码协议,得到Http请求对象,
    3. 处理 HTTP 请求,得到一个结果,封装成一个 HttpResponse 对象,
    4. 编码协议,将结果序列化字节流,
    5. 写 Socket,将字节流发给客户端,
  5. 继续循环步骤3。

根据以上的数据传输流程,我们可以提出一些问题:

  1. 如何约定字节流长度格式,以保证每次读到的字节流都是最新的而不会和上次重复;
  2. 传输字节流的编解码问题;
  3. 一个服务端肯定会有多个客户端链接,如何管理众多的客户端链接,比如如何维护断线重连,连接超时以及关闭机制;

上面这些问题我们在接下来的 Netty 学习中都会找到答案。

Netty 核心组件

在还未入门 Netty 之前我们先了解一下 Netty 里面都有哪些类,做到有的放矢,后面学习带着这些关键信息不回乱。

Bootstrap、ServerBootstrap

一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

Future、ChannelFuture

在 Netty 中所有的 IO 操作都是异步的,不会立刻知道某个事件是否完成处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,用来注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

Channel

Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel 为用户提供:

  1. 当前网络连接的通道的状态(例如是否打开,是否已连接);
  2. 网络连接的配置参数 (例如接收缓冲区大小);
  3. 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成;
  4. 调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方;
  5. 支持关联 I/O 操作与对应的处理程序。

不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

下面是一些常用的 Channel 类型:

1.  NioSocketChannel,异步的客户端 TCP Socket 连接;
2.  NioServerSocketChannel,异步的服务器端 TCP Socket 连接;
3.  NioDatagramChannel,异步的 UDP 连接;
4.  NioSctpChannel,异步的客户端 Sctp 连接;
5.  NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

Selector

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。

当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询 (Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

NioEventLoop

NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:

1.  I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发;
2.  非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。

两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。

NioEventLoopGroup

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程 (NioEventLoop) 负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

ChannelHandler

ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。

ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:

  1. ChannelInboundHandler 用于处理入站 I/O 事件;
  2. ChannelOutboundHandler 用于处理出站 I/O 操作。

或者使用以下适配器类:

  1. ChannelInboundHandlerAdapter 用于处理入站 I/O 事件;
  2. ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作;
  3. ChannelDuplexHandler 用于处理入站和出站事件。

ChannelHandlerContext

保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

ChannelPipline

保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。它实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应。

关于 Netty 的简介就先说这么多。下面的章节就带着 Socket 通信应该解决的问题 和 上面提到的Netty关键组件我们一起看看Netty是如何实现高性能网络通信的。


推荐阅读
  • 2022.4.2学习成果
    Flink中的编程模型4.1编程模型在Flink,编程模型的抽象层级主要分为以下4种,越往下抽象度越低,编程越复杂,灵活度越高。这里先不一一介绍,后续会做详细说明。这4层中,一般用 ... [详细]
  • IP双栈环境下网络应用迁移
    IPv4向IPv6迁移有多种途径,在选择具体的迁移方式时,当前环境中运行的应用是否支持IPv6是重要的考量因素之一,同时在编写新的应用时,需要考虑新编写的应用不仅可以适应当前主流的IPv4环境, ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • POCOCLibraies属于功能广泛、轻量级别的开源框架库,它拥有媲美Boost库的功能以及较小的体积广泛应用在物联网平台、工业自动化等领域。POCOCLibrai ... [详细]
  • HTTP协议相关的网络经典五层模型
    网络通信相关概念的讲解–网络协议分层(经典五层模型)在我们了解HTTP相关内容之前我们先来了解一下“网络协议分层”相关内容,因为这个是我们了解HTTP相关内容的前提条件;大家有一 ... [详细]
  • c# java socketn 字节流_C#Socket编程详解(一)TCP与UDP简介
    一、TCP与UDP(转载)1、TCP1.1定义TCP(TransmissionControlProtocol)传输控制协议。是一种可靠的、面向连接的协议(eg:打电话)、传输效率低 ... [详细]
author-avatar
me
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有