了解Netty,从IO开始

news2024/11/15 23:22:00

java程序员要想升级高级工程师或者成为架构师,绕不开Netty的学习,就算你不做IM即时通信,也不是网络编程的工作岗位,仅仅只是CRUD程序员,当你想要了解一下Dubbo、Redis、kafka、rabbitMQ、ES、zookeeper、nginx等等的底层原理或者是源码时,你会发现他们在底层实现上都用了Netty。
那么什么是Netty呢:Netty是一个高性能、异步事件驱动的NIO框架,提供了对TCP、UDP和文件传输的支持,核心功能是让客户端和服务端两者之间进行通信交流。简单说,是对TCP/UDP编程进行了简化和封装,提供了更容易使用的网络编程接口,但凡是用java开发的跟网络IO相关的中间件,都少不了Netty的影子。
那么如何来学习Netty呢,首先我们要理解IO、NIO等是什么意思。

Java I/O

在 Java 的 IO 体系中,类将近有 80 个,基本位于java.io包下,初步看起来感觉非常复杂,但是经过一番梳理之后,你会发现还是有规律可循的。
从传输数据的格式角度看,可以大致分为两组:

基于字节操作的 I/O 接口:InputStream 和 OutputStream
基于字符操作的 I/O 接口:Reader 和 Writer

从传输数据的方式角度看,也可以大致分为两组:

基于磁盘操作的 I/O 接口:File
基于网络操作的 I/O 接口:Socket

Socket

Socket:这篇里面要说的Socket,在java中Socket类其实并不在java.io包下,是在java.net包下。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,是应用层和传输层之间的接口,用于在网络上实现进程之间的通信。Socket是指两个不同计算机之间的通信链路,包括IP地址和端口号。所以其实它并不在TCP五层模型中,算是TCP/UDP的封装,实在要算也应该算是传输层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
通俗一点来理解,在网络中,通过IP地址找到网关,通过Mac地址找到子网内的机器,通过TCP/UDP协议帮我们建立端口-端口之间的通信,而一台机器中一个端口将值对应一个应用程序。socket封装了IP+端口,如果在客户端和服务器端都有一个socket封装了对方的ip和端口,那这两个socket就能相互传递信息,就如同我们每家安装的电话主机一样。

比较典型的基于 Socket 通信的应用程序场景,如下图:
在这里插入图片描述
在这里插入图片描述
简单看一个例子,了解一下Socket工作流程:

//这是客户端代码
public static void main(String[] args) throws IOException {
    //通过IP和端口与服务端建立连接
    Socket socket =new Socket("127.0.0.1",8080);
    //将字符流转化成字节流,并输出
    BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    String str="Hello,我是客户端!";
    bufferedWriter.write(str);
    bufferedWriter.flush();
    bufferedWriter.close();
}
//这是服务器端代码
public static void main(String[] args) throws Exception {
    //初始化服务端socket并且绑定 8080 端口
    ServerSocket serverSocket = new ServerSocket(8080);
    //循环监听所有连接的客户端请求
    while (true){
        try {
            //等待客户端的连接,会阻塞在accep
            Socket socket = serverSocket.accept();
            //将字节流转化成字符流,读取客户端输入的内容
            BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //读取一行数据
            String str = bufferedReader.readLine();
            //输出打印
            System.out.println("服务端收到客户端发送的信息:" + str);
        } catch (Exception e) {
        }
    }
}

我们先启动服务器端,注意,服务器端的serverSocket.accept()其实是一直阻塞等待的,我们再运行客户端,客户端发来一个连接请求,服务器端接收到后就会打印出“服务端收到客户端发送的信息:Hello,我是客户端!”这个数据,处理完这一次之后,再继续循环等待新的通信数据。

那Socket和I/O模型有什么关系呢?

在服务端S和客户端C建立好Socket连接后,假设C向S发送了一条报文信息,服务器端操作系统收到之后,应用程序需要去操作系统中读取Socket里的报文信息(recvfrom命令),然后操作系统需要将报文数据从内核空间复制到用户空间,给到应用程序去处理。

  1. 如果这个过程中我们应用程序一直等到有数据发过来我们才处理,不做别的事情,就叫做阻塞IO,就好比我们一直坐在沙发上等一个电话,不干别的事。
  2. 如果我们边干别的事,边看电话有没有来电,这个过程我们不闲着,那么就叫非阻塞IO。
  3. 如果我们一直坐在沙发上等,但是我们同时盯着好几个电话,那就叫IO多路复用。
  4. 如果我们先干别的事,让电话来的时候会响铃提示我们,响了我们再来接电话,就叫做信号驱动IO模型。
  5. 如果我们再给电话一个功能叫免提,或者蓝牙耳机,不用拿着听筒接了,那么我们甚至可以边干别的事,别接听电话,这就叫异步IO。

上面这几种情况就对应我们接下来要讲的五种IO模型。

五种I/O模型

参考:《大白话详解5种网络IO模型》

阻塞IO(BIO)

blocking I/O的缩写,同步并阻塞(传统阻塞型)从下图可以看到,不管有无数据报到来,进程(线程)是阻塞于recvfrom系统调用的。这是什么意思呢?说白了就是假如我们要用套接字读取数据,此时我们必然会调用read方法,此时这个read方法就会触发操作系统内核的一次recvfrom系统调用。
在这里插入图片描述

非阻塞IO(NIO)

non-blocking I/O的缩写,同步非阻塞,当内核中的数据报还没准备好,此时recvfrom系统调用立即返回一个EWOULDBLOCK错误,即不会将用户进程(线程)置于阻塞状态。当内核中的数据报已经准备好时,此时recvfrom系统调用,用户进程(线程)还是会阻塞,直到内核中的数据报已经拷贝到了用户空间,此时用户进程(线程)才会被唤醒来处理接收的数据报。在这里插入图片描述

IO复用

与上面NIO不同的是recvfrom操作换成了select,区别是一个线程的select操作可以选择多个文件描述符,而recvfrom每次只能选一个。用一个用户线程就能监听不同channel的OP_CONNECT,OP_ACCEPT,OP_READ和OP_WRITE这些就绪事件,然后根据某个就绪事件拿到相应的channel来做对应的操作。另一个区别是select是阻塞的,虽然它能同时监听多个连接多个文件描述符。
文件描述符(fd):分别对应Java NIO中的OP_CONNECT,OP_ACCEPT,OP_READ和OP_WRITE就绪事件,下面详细介绍IO多路复用时会用到。
在这里插入图片描述

信号驱动IO

信号驱动IO模型在等待数据报期间是不会阻塞的,即用户进程(线程)发送一个sigaction系统调用后,此时立刻返回,并不会阻塞,然后用户进程(线程)继续执行;当数据报准备好时,此时内核就为该进程(线程)产生一个SIGIO信号,此时该进程(线程)就发生一次recvfrom系统调用将数据报从内核复制到用户空间,注意,这个阶段是阻塞的。
在这里插入图片描述

异步IO(AIO)

Asynchronous I/O的缩写,异步非阻塞,异步IO模型也很好理解,即用户进程(线程)在等待数据报和数据报从内核拷贝到用户空间这两阶段都是非阻塞的,即用户进程(线程)发生一次系统调用后,立即返回,然后该用户进程(线程)继续往下执行。当内核把接收到数据报并把数据报拷贝到了用户空间后,此时再通知用户进程(线程)来处理用户空间的数据报。也就是说,这一些列IO操作都交给了内核去处理了,用户进程无须同步阻塞,因此是异步非阻塞的。
在这里插入图片描述

总结一下:

从下图可以看出,除了异步IO,其它四种IO的第二步调用recvfrom其实都是阻塞的,第一步通过主动检查或者被动通知的方式实现了非阻塞的能力。
在这里插入图片描述

IO多路复用

IO多路复用(IO Multiplexing)指单个进程/线程可以同时处理多个I/O请求。在unix系统中有三个函数select、poll、epoll,对应三种方式处理连接。
在这里插入图片描述

select

从下图可以看到,在调用select时,第3步中需要将我们所有监听的文件描述符(fd)传入内核空间中,遍历fd,如果有客户端client传入数到FDBuffer中,就会检测到某个fd就绪了,获取到就绪的fd去给服务应用程序处理。
缺点是fd_set数组有1024个限制,而且每次遍历获取就绪的fd,时间复杂度都是O(n)
在这里插入图片描述

poll

poll与select处理流程类似,只是修改了存储文件描述符的fd_set从数组改成了链表,这样就没有了1024的限制。

epoll(redis使用的就是这个模型)

针对select和poll的缺点,epoll做了几个优化,首先存放监控的文件描述符的fd_set改成了红黑树,然后就绪的文件描述符fd_set改成了双向链表,这样读取就绪文件描述符的时间复杂度就变成了O(1)。
client在向服务器传输数据后,就会把准备就绪的数据写入双向链表中,当有事件就绪的时候,epoll_wait只需要去检测就绪的链表中是否有数据就可以了。
执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据,所以当一个socket上有数据到了,内核再把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
另一个优化是通过内存映射(mmap),不需要从用户空间频繁拷贝数据到内核空间。
在这里插入图片描述

NIO-reactor模型

如下图,传统IO模型,一个连接就会分配一个线程处理请求,这样连接数多了之后,就需要分配大量的线程去处理,每个线程不仅需要分配大量的内存空间,而且线程之间的上下文切换也会极大的消耗cpu资源。如果一个连接一直没有请求,那么这个线程也将一直空置,对资源是极大的浪费。
在这里插入图片描述

在java1.4之后,java提供了NIO的相关API,帮助我们可以避免上面的问题。NIO编程的本质是以事件驱动来处理我们的网络事件,Reactor就是基于这套API提供的一套IO模型。
Reactor模型思想
分而治之 + 事件驱动(优点:模块化、高性能————把大拆小,减少阻塞时间)
分而治之:一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send(write)这几步。Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
事件驱动:相应的Task(accept、read、write)对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Acceptor和Handler执行。
Reactor三大组件
Reactor:事件分派器,将I/O事件分派给对应的Handler和Acceptor。
Acceptor:多路复用器,处理客户端新连接,创建handler。
Handler:事件处理器,有读写请求过来时,进handler处理。

单reactor单线程模型(redis基于这种实现)

一个请求进来后,如果是连接请求,会交给accecptor创建一个对应的handler。如果是非连接请求,如读写请求,则会有一个分发器dispatch交给这个链接对应的handler来处理。
在这里插入图片描述

单reactor多线程模型

单线程模型无法充分利用我们的多核cpu的优势,同时也会因为处理速度慢导致事件堆积。相比单线程模型,单reactor多线程模型增加了线程池,
在这里插入图片描述

主从reactor多线程模型(Netty、nginx基于这种实现)

一个reactor接收所有线程的请求和响应,也会存在性能问题,所以衍生出第三种主从reactor多线程模型。mainReactor只处理acceptor相关的请求,其它的handler处理的请求都交给subReactor去处理。
在这里插入图片描述

Netty

核心组件

Bootstrap和ServerBootstrap:当需要连接客户端或者服务器绑定指定端口时需要使用Bootstrap,ServerBootstrap有两种类型,一种是用于客户端的Bootstrap,一种是用于服务端 的ServerBootstrap。
Channel:相当于socket,与另一端进行通信的通道,具备bind、connect、read、write等IO操作的能力。
EventLoop:事件循环,负责处理Channel的IO事件,一个EventLoopGroup包含多个EventLoop,一个EventLoop可被分配至多个Channel,一个Channel只能注册于一个EventLoop,一个EventLoop只能与一个Thread绑定。
ChannelFuture:channel IO事件的异步操作结果。
ChannelHandler:包含IO事件具体的业务逻辑。
ChannelPipeline:ChannelHandler的管道容器。

Netty的高性能

从宏观来讲,Netty的高性能主要在于:Reactor模式、Zero Copy(零拷贝)和对象池
通过设置不同的启动参数,Netty可以同时支持单Reactor单线程模型、单Reactor多线程模型和主从Reactor多线层模型。
Netty 通过 Reactor 模型基于多路复用器接收并处理用户请求,内部实现了两个线程池, boss 线程池和 worker 线程池,其中 boss 线程池的线程负责处理请求的 accept 事件,当接收 到 accept 事件的请求时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 worker 线程池,其中 work 线程池负责请求的 read 和 write 事件,由对应的 Handler 处理。
1. Netty线程模型–Reactor 模型
在这里插入图片描述
2、Zero Copy
Zero Copy技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。举例来说,如果要读取一个文件并通过网络发送它,传统方式下每个读/写周期都需要复制两次数据和切换两次上下文,而数据的复制都需要依靠CPU。通过零复制技术完成相同的操作,上下文切换减少到两次,并且不需要CPU复制数据。

参考《Netty 中的零拷贝机制》

3、对象池
对象池模式(The Object Pool Pattern)是单例模式的一个变种,对象池模式管理一个可代替对象的集合,组件从池中借出对象,用它来完成一些任务并当任务完成时归还该对象。Netty中的Recycler,该类是个容器,基于ThreadLocal实现的的轻量级对象池,内部主要是一个Stack结构。当需要使用一个实例时,就弹出,当使用完毕时,就清空后入栈。

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

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

相关文章

群硕与Microsoft Dynamics全球团队密切协作,加速ERP产品迭代

群硕具备强大的软件研发能力,搭建自动化测试平台,保证高质量交付。 ERP系统的引入被视为企业走向数字化转型的关键一步。 此系统有助于实现企业内部资源与外部资源的整合,通过软件把人、财、物、产、供、销及相应的物流、信息流、资金流、管…

大数据之LibrA数据库系统部署方案

组网方案 基本概念 FusionInsight LibrA集群的组网方案中包含如下节点,如表1所示。 网络平面类型 FusionInsight LibrA整个系统网络划分为2个平面,即业务平面和管理平面,两个平面之间采用物理隔离的方式进行部署,保证业务、管理…

Lua快速入门教程

文章目录 1、Linux安装Lua2、语法练习2.1、变量2.2、循环2.3、函数2.4、数组2.5、迭代器2.6、Table操作2.7、Lua 模块与包2.8、加载机制2.9、Lua 元表(Metatable) 3、Lua 协同程序(coroutine)4、文件IO操作4.1、简单模式4.2、完全模式 5、错误处理 内容来源菜鸟教程&#xff0c…

软考-访问控制技术原理与应用

本文为作者学习文章,按作者习惯写成,如有错误或需要追加内容请留言(不喜勿喷) 本文为追加文章,后期慢慢追加 by 2023年10月 访问控制概念 访问控制是计算机安全的一个重要组成部分,用于控制用户或程序如…

Linux | gdb的基本使用

目录 前言 一、调试文件的生成 二、调试指令 1、选择调试文件 2、查看代码 3、运行代码 4、断点 5、打印与常显示 6、其他 总结 前言 前面我们学习了如何使用gcc/g来进行对代码进行编译,本章我们将使用gdb来对代码进行调试,学习本章的前提是有…

实验室用超声波清洗机哪家好

随着超声波清洗机在实验室得到广泛应用,超声波清洗机厂家也随之增多,品牌、型号更是数不胜数,价格相差也是十分悬殊。那么面对纷繁复杂的实验室超声波清洗机市场,实验室用超声波清洗机哪家好?小编推荐国内知名超声波清…

《动手学深度学习 Pytorch版》 9.3 深度循环神经网络

将多层循环神经网络堆叠在一起,通过对几个简单层的组合,产生一个灵活的机制。其中的数据可能与不同层的堆叠有关。 9.3.1 函数依赖关系 将深度架构中的函数依赖关系形式化,第 l l l 个隐藏层的隐状态表达式为: H t ( l ) ϕ l …

用宝塔部署静态html页面

云服务器安装宝塔面板搭建LNMP环境,可以看另一文零基础搭建个人网站详细流程。 本文主要介绍用宝塔部署静态页面,比较简单,以部署一个用户协议为例。 首先,去“网站”中“添加站点”。有域名用域名,没域名用IP。 然后…

NSSCTF做题(10)

叫10好听一点,就是补9的 第7页的内容 [SWPUCTF 2022 新生赛]ez_sql get传参说是不安全,那就只能用post了 有回显了,两个假的flag 发现万能密码 1 or 11#变成了 11# 11# 1 11#1# 11# 11# 发现or和空格都无了,union也过滤 …

零基础Linux_19(进程信号)产生信号+Core_Dump+保存信号

目录 1. 信号前期知识 1.1 生活中的信号 1.2 Linux中的信号 1.3 信号概念 1.4 信号处理方法的注册 2. 产生信号 2.1 通过终端按键产生信号 2.2 调用系统调用向进程发信号 2.3 软件条件产生信号 2.4 硬件异常产生信号 3. 核心转储Core Dump 4. 保存信号 4.1 信号在…

day08_面向对象_封装_继承_this_super_访问修饰符

今日内容 1.作业 2.封装 3.继承 4.this和super 5.访问修饰符 零、复习 成员变量和局部变量(画表格) this的作用 this是当前对象,当前方法的调用者this可以调用属性和方法this.属性, this.方法名(),this() 构造方法的作用和语法特征 作用: 创建对象,属性初始化特征: 没有返回值,…

数据结构和算法(13):优先级队列

概述 按照事先约定的优先级,可以始终高效查找并访问优先级最高数据项的数据结构,也统称作优先级队列 优先级队列将操作对象限定于当前的全局极值者。 根据数据对象之间相对优先级对其进行访问的方式,与此前的访问方式有着本质区别&#xf…

PostgreSQL与MySQL数据库对比:适用场景和选择指南

数据库是现代应用程序的基石之一,而在选择合适的数据库管理系统(DBMS)时,开发者常常会面临着许多选择。在这方面,PostgreSQL和MySQL是两个备受瞩目的选项。本文将深入研究这两者之间的异同,并为您提供适用场…

疯狂星期四的营销策略是什么?如何复制?

你知道疯狂星期四吗?它的策略是什么?如何对标它写一个类似的方案呢? 1、消费者心理 KFC疯狂星期四的核心目标消费者是对价格敏感的年轻人和家庭消费者。他们寻求物有所值的美食体验,希望在合理的价格范围内享受到美味的食物。通过…

Unity之ShaderGraph如何实现UV抖动

前言 今天我们通过噪波图来实现一个UV抖动的效果。 如下图所示: 关键节点 Time:提供对着色器中各种时间参数的访问 UV:提供对网格顶点或片段的UV坐标的访问。可以使用通道下拉参数选择输出值的坐标通道。 SimpleNoise:根据…

论文导读 | 支持事务与图分析的图存储系统

事务系统保证了系统的数据一致性,确保事务更新的原子性或是不同事务之间的数据隔离性等在多线程并发环境下所必不可少的ACID特性。而在今天快速变化的商业环境下,诸如物流和供应链,金融风控和欺诈检测等场景都需要图分析系统提供对数据动态更…

Node.js在Python中的应用实例解析

随着互联网的发展,数据爬取成为了获取信息的重要手段。本文将以豆瓣网为案例,通过技术问答的方式,介绍如何使用Node.js在Python中实现数据爬取,并提供详细的实现代码过程。 Node.js是一个基于Chrome V8引擎的JavaScript运行时环境…

SEAL:RLWE-BFV 开源算法库

参考文献: GitHub - microsoft/SEAL: Microsoft SEAL is an easy-to-use and powerful homomorphic encryption library.[HS13] Halevi S, Shoup V. Design and implementation of a homomorphic-encryption library[J]. IBM Research (Manuscript), 2013, 6(12-15…

UML类图关系(泛化 、继承、实现、依赖、关联、聚合、组合)

在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition)&#x…

【python零基础入门学习】python进阶篇之OOP - 面向对象的程序设计

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8…