为什么选择Netty
- 原生的NIO有JDK epoll空轮询bug
bug描述
对于突然中断的Socket连接,jdk底层的对epoll的实现可能出现bug,这个bug表现为selector.select()不会阻塞,而是直接返回,此时是并没有事件发生的。因此select()的返回值会为0,那么while循环就会一直循环执行,最终可能导致cpu占用率达到100%
Netty如何解决这个问题的?
既然是selector出现了问题,那么就当然是应当要重建selector了。netty会维护一个记录当前select()返回值为0的次数,如果次数大于netty的规定值,那么会调用rebuilder()对其进行重建
重建步骤:
1 新建seletor()
2 将原来selector上的channel注册到新的channel上
-
Nio的api比较复杂
-
原生Nio开发需要对线程池特别熟悉,因为涉及到Reactor模型
Reactor线程模型
Reactor模式是基于IO多路复用的,IO多路复用主要表示为,用一条线程,监听多个感兴趣的事件。Netty的reactor模式中有2个reactor,1个用于监听新连接,另一个用于监听已建立的连接的活动。Netty为了充分利用多核cpu还采用了线程池
TCP 粘包/拆包
https://www.cnblogs.com/wade-luffy/p/6165671.html
是什么
由于Tcp是面向字节流的,而服务端又存在着流量控制这样的策略,因此,对于服务端,一次性读到多少字节的数据是不确定的,因此会发生第一次发送的包与第二次发送的包粘在一起,或者第一次发送的包被分割为2份的情况。
怎么解决
1 对数据进行定长。如规定每次发生的数据都为200byte,不足的部分补空字符
2 将数据分为数据头与数据体的2部分,数据头中存放整个数据的长度(或数据体部分的长度)。http1.1为了采用管道这种方式,也就在http头增加了一个content-length
3 在数据结束部分添加结束符号
Netty 的零拷贝
在nio的学习中,我们知道,nio将最耗时的io操作(填充,读取缓冲区)转移到了操作系统,这部分内存(ByteBuffer)被称为堆外内存。
是在发送数据的时候,传统的实现方式是:
1. `File.read(bytes)`
2. `Socket.send(bytes)`
这种方式需要四次数据拷贝和四次上下文切换:
1. 数据从磁盘读取到内核的read buffer
2. 数据从内核缓冲区拷贝到用户缓冲区
3. 数据从用户缓冲区拷贝到内核的socket buffer
4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区
明显第2,3部分是无用的,因为我们根本不需要将数据读到用户内存中。
零拷贝
1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer
上面的两次操作是居于DMA的操作,因此不需要CPU参与,所以就达到了零拷贝。