BIO和NIO
BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
为什么用Netty
- 统一API,支持阻塞和非阻塞
- 简单而强大的线程模型
- 自带编码器解决TCP粘包、拆包问题
- 自带各种协议栈(FTP,SMTP,HTTP 以及各种二进制和基于文本的传统协议)
线程模型
靠NioEventLoopGroup线程池来实现具体的线程模型
- bossGroup:接收连接
- workerGroup:处理具体的请求,交由Handler处理
Netty长连接,心跳机制
我们知道 TCP 在进行读写之前,server 与 client 之间必须提前建立一个连接。建立连接的过程,需要我们常说的三次握手,释放/关闭连接的话需要四次挥手。这个过程是比较消耗网络资源并且有时间延迟的。
在 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候, client 与 server 之间如果没有交互的话,它们是无法发现对方已经掉线的。为了解决这个问题, 我们就需要引入 心跳机制。
心跳机制原理:在client与server之间在一定时间内没有时速局交互,即处于idle状态时,客户端活服务器就会发送一个特殊的数据报文,回应发送方,此即一个ping-pong交互,所以当某一端收到心跳消息后,就知道了对方仍然在线,这就确保了TCP连接的有效性。netty的实现类是IdleStateHandler
netty的零拷贝
在OS层面的零拷贝是指避免在用户态与内核态之间来回拷贝数据,而在netty层面主要体现在对于数据操作的优化
-
缓冲区
-
读
进程发起read请求之后,内核接收到read请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据copy给进程的缓冲区;如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核read缓冲区,这一步通过DMA完成;接下来就是内核将数据copy到进程的缓冲区;
-
发
如果进程发起write请求,同样需要把用户缓冲区里面的数据copy到内核的socket缓冲区里面,然后再通过DMA把数据copy到网卡中,发送出去;
-
提供两种方式 mmap+write方式;sendfile方式;
-
-
虚拟内存
所有现代操作系统都使用虚拟内存,使用虚拟内幕草你的地址取代物理地址,好处是
- 一个以上的虚拟地址可以指向同一个物理内存地址
- 虚拟内存空间可大于实际可以的物理地址
利用第一个特性可以把内核空间地址和虚拟地址映射到同一个物理地址,这样DMA就可以填充对内核和用户空间进程同时可见的缓冲区了,省去了内核和用户空间的往来拷贝
java对零拷贝的支持
-
mmap+write
-
sendfile
netty中零拷贝的体现
-
使用 Netty 提供的 CompositeByteBuf 类, 可以将多个ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝。
-
ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝。
-
通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题.