Netty权威指南 第2章NIO 入门读书笔记

Stella981
• 阅读 666

2.1 传统的BIO编程

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。

如果不创建线程,还是在主线程中处理请求,则整个服务端是单线程处理能力, 待第一个客户端请求关闭后,处理第二个客户端请求。

2.2 伪异步I/O编程

当有新的客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和N个活跃线程,对消息队列中的任务进行处理。

public int read(byte b[]) throws IOException{

       return read(b,0,b.length);

}

当对Socket的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。

       有数据可读;

       可用数据已经读取完毕;

       发生空指针或者I/O异常。

读和写操作都是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络I/O的传输速度。

2.3 NIO编程

非阻塞I/O(Non-block I/O)

与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。

2.3.1 NIO类库简介

1.缓冲区 Buffer

在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。

BtyeBuffer:字节缓冲区 CharBuffer 字符缓冲区

ShortBuffer:短整型缓冲区 IntBuffer:整型缓冲区

LongBuffer:长整型缓冲区 FloatBuffer:浮点型缓冲区

DoubleBuffer:双精度浮点型缓冲区

2.通道Channel

Channel是一个通道,它就像自来水管一样,网络数据通过Channel读取和写入。

Channel是全双工的,同时支持读写操作。

自顶向下看,前三层主要是Channel接口,用于定义它的功能,后面是一些具体的功能类(抽象类)。

实际上Channel可以分为两大类,用于网络读写的SelectableChannel和用于文件操作的FileChannel。

本书涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

3.多路复用器Selector

      多路复用器提供选择已经就绪的任务的能力,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

一个多路复用器Selector可以同时轮询多个Channel,epoll可以支持接入成千上万的客户端。

2.3.2 NIO服务端序列图

步骤1:

打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道。

步骤2:

绑定监听端口,设置连接为非阻塞模式。

步骤3:

创建Reactor线程,创建多路复用器并启动线程。

步骤4:

将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件。

步骤5:

多路复用器在线程run方法的无限循环体内轮询准备就绪的Key。

步骤6:

多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP 三次握手,建立物理链路。

步骤7:

设置客户端链路为非阻塞模式。

步骤8:

将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息。

步骤9:

异步读取客户端请求消息到缓冲区。

步骤10:

对ByteBuf进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排。

步骤11:

将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。

2.3.4 NIO 客户端序列图

步骤1:打开SocketChannel,绑定客户端本地地址。

步骤2:设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数。

步骤3:异步连接服务端。

步骤4:判断是否连接成功,如果连接成功,则直接注册状态位到多路复用器中,如果当前没有连接成功(异步连接,返回false,说明客户端已经发送sync包,服务端没有返回ack包,物理链路还没有建立)。

步骤5:向Reactor线程的多路复用器注册OP_CONNECT状态位,监听服务端的TCP ACK 应答。

步骤6:创建Reactor线程,创建多路复用器并启动线程。

步骤7:多路复用器在线程run方法的无限循环体内轮询准备就绪的Key。

步骤8:接收connect事件进行处理。

步骤9:判断连接结果,如果连接成功,注册读事件到多路复用器。

步骤10:注册读事件到多路复用器。

步骤11:异步读客户端请求消息到缓冲区。

步骤12:对ByteBuffer进行编解码,如果有半包消息接收缓冲区Reset,继续读取后续的报文,将解码成功的消息封装成Task,

投递到业务线程池中,进行业务逻辑编排。

步骤13:将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端。

使用NIO编程的优点:

1)客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECT等待后续结果,不需要像之前的客户端那样被同步阻塞。

2)SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信线程就可以处理其它的链路,不需要同步等待这个链路可用。

3)线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,因此,它非常适合做高性能、高负载的网络服务器。

2.4 AIO编程

异步通道提供以下两种方式获取操作结果:

通过java.util.concurrent.Future类来表示异步操作的结果;

在执行异步操作的时候传入一个java.nio.channels.CompletionHandler接口的实现类作为操作完成的回调。

2.5 4种I/O的对比

 

同步阻塞I/O(BIO)

伪异步I/O

非阻塞I/O(NIO)

异步I/O(AIO)

客户端个数:I/O线程

1:1

M:N(其中M可以大于N)

M:1(1个I/O线程处理多个客户端连接

M:0(不需要启动额外的I/O线程,被动回调

I/O类型(阻塞)

阻塞I/O

阻塞I/O

非阻塞I/O

非阻塞I/O

I/O类型(同步)

同步I/O

同步I/O

同步I/O(I/O多路复用)

异步I/O

API使用难度

简单

简单

非常复杂

复杂

调试难度

简单

简单

复杂

复杂

可靠性

非常差

吞吐量

2.6 选择Netty的理由

2.6.1不选择Java原生NIO编程的原因

  • NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
  • 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。
  • 可靠性能力补齐,工作量和难度都非常大。
  • JDK NIO的BUG。

2.6.2为什么选择Netty

  • API使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
  • 成熟,稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
  • 经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。

2.7 总结

通过一个简单的demo开发,即时间服务器程序,让大家熟悉传统的同步阻塞I/O、伪异步I/O、非阻塞I/O(NIO)和异步I/O(AIO)的编程和使用差异。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
zdd小小菜鸟 zdd小小菜鸟
2年前
Netty面试
Netty面试1.BIO、NIO和AIO的区别?BIO:tex一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源
Wesley13 Wesley13
3年前
Java NIO
简介引入NIO的原因1.因为BIO都是阻塞的IO,为了使Java能支持非阻塞I/O,JDK引入了NIO,可以将NIO理解成是NonblockI/O.(也有书说是newIO)2.BIO编程中,每当有一个新的客户端请求过来时,服务器端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接,在并发量
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Netty面试题
1.BIO、NIO和AIO的区别?BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这