首页 > 编程语言 >Netty权威指南:Netty总结-Java I/O

Netty权威指南:Netty总结-Java I/O

时间:2024-09-08 11:53:04浏览次数:10  
标签:指南 Netty Java 异步 阻塞 Selector 线程 Channel 客户端

第一章 Java I/O

1.1 I/O基础入门

Java1.4之前的版本,开发高性能I/O程序的时候,有问题:

  • 没有数据缓冲区,I/O性能有问题
  • 没有Channel概念,只有输入输出流
  • 只有BIO,通常会导致通信线程被长时间阻塞
  • 支持字符集有限,硬件移植性不好

1.1.1 Linux网络I/O模型

Linux的内核将所有的外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统提供的系统命令,返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也有相应的描述符,称为socketfd(socket描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。

Unix提供了五种I/O模型:

  1. 阻塞I/O模型:所有文件操作都是阻塞的,
  2. 非阻塞I/O模型:recvfrom时,如果缓冲区没有数据就返回一个EWOULDBLOCK错误,然后一直轮询检查
  3. I/O复用模型:Linux提供select/poll,进程通过将一个或多个fd传递给Select或poll,阻塞到Select上,这样就可以检测多个fd是否处于就绪状态。还提供了epoll,基于事件驱动代替select和poll的顺序扫描
  4. 信号驱动I/O模型:需要开启套接口信号驱动I/O功能,通过系统调用sigaction执行信号处理函数,接收到信号就回调然后recvfrom执行。
  5. 异步I/O:告知内核启动某个操作,内核完成操作后再通知。

1.1.2 I/O多路复用

可以同时处理多个客户端请求,把多个I/O阻塞复用到同一个Select的阻塞上,不需要新的进程或线程,系统开销小

select有缺陷,epoll改进:

  1. 一个进程打开的socketfd不受限制,仅受限于操作系统的最大文件句柄数

  2. I/O效率不会随着FD的数目增加而线性下降

    epoll只会对活跃的socket进行操作,因为epoll是根据每个fd的callback函数实现的,只有活跃的socket才会去主动调用这个函数。

  3. 使用mmap加速内核与用户空间的消息传递

  4. epoll的api更加简单

1.2 Java的I/O演进

JDK1.4推出NIO,主要的类和接口:

  • 缓冲区ByteBuffer
  • 管道Pipe
  • 进行I/O操作的Channel,包括ServerSocketChannel和SocketChannel
  • 多种字符集的编码和解码能力
  • 实现非阻塞I/O操作的多路复用器selector
  • 基于流行的Perl实现的正则表达式类库
  • 文件通道FileChannel

依旧有一些问题:

  • 没有统一的文件属性
  • api能力弱,目录的级联创建和递归遍历需要自己实现
  • 底层存储系统的一些高级api无法使用
  • 文件操作不支持异步读写

JDK1.7升级了NIO,

  • 提供批量获取文件属性的API,api具有平台无关性
  • 提供aio
  • 完成通道功能

第二章 NIO入门

2.1 传统BIO编程

ServerSocket负责绑定IP地址,启动监听端口,Socket负责发起连接,连接成功后,双方通过输入和输出流进行同步阻塞式通信

BIO通信模型图

在这里插入图片描述

可以看出服务端线程个数和客户端并发访问数一比一关系

并发访问量增加会导致系统的性能急速下降

2.2 伪异步I/O编程

改进上述模型,通过一个线程池来处理多个客户端的请求接入,客户端个数可以远远大于线程池最大线程数

2.2.1 伪异步I/O模型图

采用线程池和任务队列可以实现
在这里插入图片描述

当接受到新的客户端连接时,将请求Socket封装成一个Task,调用线程池的execute方法执行,避免每个请求都创建一个新的线程

没有从根本解决问题:

当对Socket的输入流进行读取操作时,会一直阻塞。依赖于对方的处理速度,可靠性比较差

2.3 NIO编程

  • Non-Block I/O非阻塞式I/O

  • 与ServerSocket和Socket对应,提供ServerSocketChannel和SocketChannel

2.3.1 NIO 类库

面向块的I/O,通过块处理数据

  1. 缓冲区 Buffer

    • Buffer是一个对象,包含要写入或读取的数据。
    • 实质是一个数组。通常是一个字节数组(ByteBuffer)
    • 每一种Java基本类型(除了Boolean)都有对应的缓冲区
    • 在这里插入图片描述
  2. 通道Channel

    • 网络数据通过通道读取和写入,与流的不同之处在于Channel是双向的(读,写或同时),也就是全双工的,更好的映射了操作系统底层API
    • 可分为两类:用于网络的SelectableChannel和用于文件的FileChannel
    • 在这里插入图片描述
  3. 多路复用器Selector

    • 具有选择已就绪任务的能力,也就是不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,就会被轮询出来然后通过SelectionKey获取就绪Channel的集合,进行I/O操作。
    • 一个Selector可以同时轮询多个Channel,且使用epoll代替select实现,一个Selector就可以处理成千上万的客户端

2.3.2 NIO服务端序列图

在这里插入图片描述

服务端创建过程:

  1. 打开ServerSocketChannel,用于监听客户端连接
  2. 绑定监听端口,设置为非阻塞模式
  3. 创建Reactor线程,创建Selector并启动线程
  4. 将ServerSocketChannel注册到Selector上,监听ACCEPT事件
  5. Selector轮询准备就绪的Key
  6. 监听到有新的连接请求,处理新的接入请求,完成TCP三次握手,建立连接
  7. 设置客户端为非阻塞模式
  8. 将新的SocketChannel注册到Selector上
  9. 异步读取客户端请求消息到缓冲区
  10. 对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排
  11. 将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端

2.3.3 服务端源码

  1. 在主线程中设定监听端口以及创建Server线程
  2. Server类中:
    1. 先构造方法,资源初始化,创建Selector和ServerSocketChannel,对Channel和TCP参数进行设置
    2. selector轮询,有处于就绪状态的Channel时注册到SelectionKey
    3. 处理客户端请求,根据SelectionKey判断类型,通过ServerSocketChannel的accept接受请求并创建SocketChannel
    4. 创建ByteBuffer读取客户端数据,调用SocketChannel的read方法读取码流,读取到Buffer以后进行解码。首先需要进行flip操作,然后再调用ByteBuffer的get操作将缓冲区可读的数据读出。
    5. 将消息回送给客户端:创建ByteBuffer,调用ByteBuffer的put方法放入ByteBuffer中,再进行flip操作,然后再通过SocketChannel的write方法写入Channel,传回给客户端。

2.3.4 NIO客户端序列图

在这里插入图片描述

步骤如图

2.3.5 客户端源码

  1. 主线程中创建ClientHandle线程
  2. ClientHandle:
    1. 构造函数初始化NIO的多路复用器和SocketChannel对象,将SocketChannel设置为异步非阻塞模式
    2. 发送连接请求,如果成功,将SocketChannel注册到Selector上,并注册SelectionKey为OP.READ,如果没有成功,注册Key为OP.CONNECT,如果服务端返回TCP syn-ack消息后,Selector就能轮询到这个SocketChannel处于就绪状态
    3. 轮询Selector,有就绪的Channel时,执行handleInput(key):
      1. 对SelectionKey判断,如果处于连接状态,调用SocketChannel的finishConnect方法,如果返回true就注册到Selector上,Key为OP.READ,失败则报出异常
    4. 对可读的SocketChannel进行读取,调用SocketChannel的read操作。完成后将stop置为true
    5. 对连接资源释放

优点

  1. 客户端发起的连接操作是异步的,可以通过在多路复用器注册OPCONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。
  2. SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待直接返回,这样I0通信线程就可以处理其他的链路,不需要同步等待这个链路可用。
  3. 线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll 实现,它没有连接句柄数的限制(只受限于操作系统的最大句柄数或者对单个进程的句柄限制),这意味着一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降。因此,它非常适合做高性能、高负载的网络服务器

2.4 AIO编程

异步非阻塞I/O,对应UNIX事件驱动I/O(AIO), 不需要通过Selector进行轮询操作就能够实现异步读写,实现比NIO简单

核心是异步通道(Asynchronous Channel),异步通道不会阻塞线程,I/O操作会立即返回,当当I/O操作完成时,系统会自动调用与之关联的CompletionHandler,或通过Future对象通知操作的完成。CompletionHandler是一个回调接口,用于处理异步操作的结果。当异步操作完成时,系统会自动调用相应的CompletionHandler方法。这个回调机制避免了线程在I/O操作上进行等待。

CompletionHandler的常用方法包括:

  • completed(V result, A attachment):异步操作成功完成时调用。
  • failed(Throwable exc, A attachment):异步操作失败时调用。

除了CompletionHandler,Java AIO还支持使用Future对象来获取异步操作的结果。使用Future的方式类似于同步编程,但操作是异步进行的。

通过调用Future的get()方法,线程可以阻塞直到操作完成,这种方式有时被用在需要混合同步和异步处理的场景中。

2.5 4种I/O对比

2.5.1 概念

  1. 异步非阻塞I/O

    NIO不能称为异步非阻塞I/O,只能称为非阻塞I/O,是基于I/O复用技术的非阻塞I/O。

    AIO是真正的异步I/O

  2. 多路复用器Selector

    JavaNIO的实现关键是多路复用I0技术,多路复用的核心就是通过Selector来轮询注册在其上的Channel,当发现某个或者多个Channel处于就绪状态后,从阻塞状态返回就绪的Channel的选择键集合,进行I/0操作。

  3. 伪异步I/O

    来源于实践,通过线程池做缓冲区

2.5.2 不同I/O模型对比

在这里插入图片描述

在这里插入图片描述

2.6 选择Netty

开发高质量的NIO程序很复杂,且调试和跟踪非常麻烦。

  1. NIO的类库和API繁杂,使用麻烦
  2. 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。
  3. 可靠性能力补齐,工作量和难度都非常大。
  4. BUG

Netty优点:

  • API使用简单,开发门槛低
  • 功能强大,预置了多种编解码功能,支持多种主流协议
  • 定制能力强,可以通过 ChannelHandler对通信框架进行灵活地扩展
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为 NIO的BUG 而烦恼
  • 社区活跃,版本迭代周期短
  • 经历了大规模的商业应用考验,质量得到验证

标签:指南,Netty,Java,异步,阻塞,Selector,线程,Channel,客户端
From: https://blog.csdn.net/weixin_56048526/article/details/142024491

相关文章

  • 软件设计之JavaWeb(1)
    软件设计之JavaWeb(1)此篇应在MySQL之后进行学习:路线图推荐:【Java学习路线-极速版】【Java架构师技术图谱】尚硅谷全新JavaWeb教程,企业主流javaweb技术栈资料可以去尚硅谷官网免费领取此章节最好学完JDBC观看学习内容:XML概述TomcatIDEA开发并部署运行WEB项目XML概......
  • 基于java网页的纸业管理系统设计与实现
    博主介绍:专注于Java.net phpphython 小程序等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作☆☆☆精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟我的博客空间发布了1000+毕设题目方便大家学习使用感兴趣的可以先收藏起来,还有大家......
  • 基于java的团购网站设计与实现
    博主介绍:专注于Java.net phpphython 小程序等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作☆☆☆精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟我的博客空间发布了1000+毕设题目方便大家学习使用感兴趣的可以先收藏起来,还有大家......
  • Java毕设项目II基于Spring Boot+Vue的失物招领平台
    目录一、前言二、技术介绍三、系统实现四、论文参考五、核心代码六、源码获取全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末一、前言在快节奏的生活中,物品遗失与寻回成为了人......
  • Java SE面试题 ——深入理解
           目录        1.什么是JDK,JRE,JVM;他们之间有什么联系    2.Java的八种数据类型 3.&和&&区别                                          4.switch的参数可以有哪些类型 5.类与对象如......
  • Java中的五种排序
    五种排序常见的排序算法有十种:三大基础排序:选择、冒泡、插入(时间复杂度:O(n^2),空间复杂度(O(1))比较低)使用的是最基本的两层嵌套结构快速、归并、堆、希尔、桶、计数、基数排序:1)升序:从小到大2)降序:从大到小1、冒泡排序法冒泡排序是一种简单排序算法,它通过以此比较交换两......
  • java(十三)参数传递
    Java中的参数传递:分为值传递和引用传递。这里讲的参数传递针对的是方法里面传递的值的时候发生的情况但本质上,Java中只有值传递。引用传递,其实可以理解为传的是类似指针,地址的东西。1.值传递只有基本数据类型采用值传递,特点是传递的是值的拷贝,传递完后两者就没有关系了。也......
  • JAVA(十五)类和对象
    面相对象编程为什么会设计类这个东西程序中要表示一个人的特征如何表示,它具有不同的类型,第一个name姓名age年龄 19intsex性别男String第二个name1姓名age1年龄 19intsex1性别男String第n个。调用第几个怎么调用,我们知道数组放的是数据相同的......
  • JAVA(十四)类和对象之面向对象编程
    编程的分类按编程风格分类面向过程编程和面向对象编程和面向接口编程1.1面向过程编程过程式编程,也称为命令式编程,是一种编程范式,它依赖于过程调用来实现逻辑。代码按照一定的顺序执行,从而实现功能。在过程式编程中,程序被组织成一系列的过程或函数调用,每个过程都负责执行特......
  • Steam家庭共享功能全解析:能否同时畅玩同一游戏及详细操作指南
    关于Steam家庭共享功能,确实规定同一时间只能有一名用户访问和游玩共享库中的某个特定游戏。这意味着即便启用了家庭共享,两个账户不能同时玩同一个游戏。不过,我为您准备了详细的Steam家庭共享设置教程,如下,一起来看看吧。Steam家庭共享设置步骤:1.登录Steam客户端:确保Steam客户......