JAVA的NIO学习笔记第一节-Unix的IO模型

Unix五种I/O模型

第一种:阻塞式IO,最常用的I/O模型就是阻塞式I/O模型

第二种:非阻塞式IO,recvfrom从应用层到内核的时候,如果该缓冲区没有数据的话,就直接返回一个EWOULDBLOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核是不是有数据到来。

第三种:I/O复用模型,Linux提供select/poll,进行通过将一个或多个fd传递给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮助我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd有限,因此它的使用受到了一些制约。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动方式代替顺序扫描,因此性能更高。当有fd就绪时,立即回调函数rollback。

第四种:信号驱动IO,由内核通知我们何时可以开始一个I/O操作。

第五种:异步IO,由内核通知我们I/O操作何时已经完成。

epoll和select模型的区别

1.支持一个进程打开的socket描述符不受限制。select最大的缺陷就是单个进程所打开的FD是有一定限制的,默认为1024。而epoll并没有限制,它所支持的FD上限是操作系统的最大文件句柄数。

2.I/O效率不会随着FD数目的增加而线性下降。select/poll模型的一个致命缺点是当你拥有一个socket集合时,由于网络延迟或者链路空闲,任一时刻只有少部分socket是活跃的,但是select/poll每次调用都会线性扫描全部的集合,导致效率线性下降。epoll不存在这个问题,它只会对活跃的socket进行操作,这是因为内核实现中采用事件驱动的方式,都有一个callback函数,只有活跃的socket才会去主动调用callback函数。

3.epoll使用mmap加速内核与用户空间的消息传递。select模型会将用户消息复制到内核空间,而epoll采用mmap映射内存的方式进行。

4.epoll的API更简单。

java中各种I/O实现方式

第一种 BIO阻塞式IO

JAVA的NIO学习笔记第一节-Unix的IO模型_第1张图片

BIO的主要问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接。

第二种是伪异步IO

JAVA的NIO学习笔记第一节-Unix的IO模型_第2张图片

伪异步IO通信框架采用了线程池实现,因此避免了每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。

第三种NIO非阻塞IO

第四种异步IO

TCP粘包/拆包问题

TCP是个“流”协议,所谓流,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

JAVA的NIO学习笔记第一节-Unix的IO模型_第3张图片

产生粘包/拆包的原因?

1.应用程序write写入的字节大小大于套接字接口发送缓冲区大小。

2.进行MSS大小的TCP分段。MSS:最大报文段长度MSS选项是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。

3.以太网帧的payload大于MTU进行IP分片。MTU(Maximum Transmission Unit)最大的传输单元。payload是post数据的大小

解决TCP/IP的拆包和粘包问题的方案?

1.消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;

2.在包尾增加回车换行符进行分割,例如FTP协议

3.将消息分为消息头和消息体,消息头中包含消息总长度。

4.采用更复杂的应用协议层。

几种常见的解决拆包粘包的方案:

1.LineBasedFreamDecoder+StringDecoder方式。工作原理是依次遍历ByteBuf中的可读字节,判断是否有换行符,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器。支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常。

2.DelimiterBasedFrameDecoder可以自定义分隔符。

3.FixedLengthFrameDecoder是固定长度的解码器,它能够按照指定的长度对消息进行自动解码。无论一次接收到多少数据包,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。

你可能感兴趣的:(JAVA的NIO学习笔记第一节-Unix的IO模型)