Linux IO模型与Java网络编程

一、网络编程Socket API的操作含义

API 阻塞 非阻塞
connect     tcp三次握手成功后返回。 立即返回,需要通过其他方式判断TCP连接建立成功还是失败。 
send     阻塞直到将待发送数据从用户空间全部放入内核发送缓冲区后返回。 立即返回,无论待发送数据是否成功放入内核发送缓冲区。
recv 阻塞直到数据到达内核接收缓冲区,并将数据从内核复制到用户空间后返回。 立即返回,无论是否有数据到达内核接收缓冲区。
close

二、IO模型划分

1、linux的IO模型划分

Linux的IO模型可划分为:“同步 阻塞IO”、“同步 非阻塞IO”、“ IO复用”、“ 信号驱动IO”和“ 异步IO”。
POSIX标准可划分为:“同步IO”和“异步IO”。
IO操作分为两个步骤:“发起IO请求”和“实际的IO操作”。
阻塞/非阻塞:IO操作的“发起IO请求”过程是否阻塞请求线程。
同步/异步:IO操作的“实际的IO读写”过程是否阻塞请求线程。
区别解释:
1)同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态
2) 同步的情况下,是由处理消息者自己去等待消息是否被触发,而异步的情况下是由触发机制来通知处理消息者;
3) 阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。 阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知不能够执行其他业务, 函数只有在得到结果之后才会返回。 非阻塞 指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回
IO模型 是否阻塞 是否同步
阻塞IO 阻塞 同步
同步非阻塞IO 非阻塞 同步
IO多路复用 阻塞     同步
AIO 非阻塞 异步

2、同步阻塞IO(blocking IO)

Linux IO模型与Java网络编程_第1张图片
特点:用户线程在IO操作执行的两个步骤中都被阻塞。
优点:数据传输及时,获得数据无延迟。
缺点:用户线程阻塞等待,性能低。

3、同步非阻塞IO(non-blocking IO)

Linux IO模型与Java网络编程_第2张图片
特点:
1)用户线程发起IO操作请求后,若IO条件不满足则立即收到失败响应,用户线程不会被阻塞。
2)用户线程需不断的主动发起IO操作请求询问内核当前IO条件是否满足。
3)实际的IO操作拷贝数据到用户空间的过程中,用户线程仍然被阻塞。
缺点:
1)用户线程需不断轮询IO条件是否满足,浪费系统资源。
2)IO任务完成的响应时间增大,导致系统整体吞吐量降低。

4、IO多路复用(IO multiplexing)

Linux IO模型与Java网络编程_第3张图片
特点:
1)select/poll/epoll调用后也会阻塞用户线程,但是用户线程是被select/poll/epoll函数阻塞,而非被阻塞在真正的IO系统调用之上。
2)单个线程可以同时处理多个网络连接的IO,select/poll/epoll会不断轮询所监听的所有socket,当某个socket有事件就绪就通知用户线程。
3)当需要同时处理多个客户端接入请求时,可以使用多线程或IO多路复用技术进行处理。其中IO多路复用的最大优势是系统开销小(系统在单线程情况下可以同时处理多个客户端请求)。

5、异步非阻塞IO(asynchronout IO)

Linux IO模型与Java网络编程_第4张图片
特点:
1) 用户线程在IO操作执行的两个步骤中都不被阻塞,可以去处理其他业务逻辑。
2)系统内核将数据复制到用户空间后,主动向用户线程发送信号通知/或调用用户线程回调函数,告诉用户线程它的IO操作执行完成了。

6、五种IO模型的比较

Linux IO模型与Java网络编程_第5张图片
non-blocking IO 与 AIO的区别:
1)在non-blocking IO中,仍然要求用户线程去主动检查IO条件是否就绪,并且到数据准备完成后也需要用户线程主动执行系统调用将数据拷贝到用户空间。
2)AIO则完全不同,它像是用户线程将整个IO操作交给了系统内核完成,系统内核做完IO操作后主动发送信号通知告知用户线程,在此期间用户线程不需要主动做任何事情。


三、常见的服务端模型

1、BIO + 主线程Acceptor + 多线程Worker

Linux IO模型与Java网络编程_第6张图片
通信过程:
1) 服务端 由一个独立的Acceptor线程负责监听(socket.accept)客户端的连接。
2) Acceptor监听到客户端连接请求后,为每个客户端socket 连接 创建一个新的Worker线程进行链路处理。
3)Worker 线程完成对客户端socket连接请求的处理后,通过输出流返回响应给客户端,然后socket连接断连,Worker线程销毁。
模型缺点:
1) BIO的读和写操作都是同步阻塞的,阻塞时间取决于对端IO线程的处理速度和网络IO的传输速度,可靠性差。

2)服务端线程个数与客户端并发访问连接数是1:1的关系。

3)随着客户端并发访问量增大,服务端线程个数线性膨胀,系统性能急剧下降。

优化方式:

1)服务端通过线程池来处理多个客户端的接入请求,通过线程池约束服务端线程资源。形成“客户端并发连接M”与“服务端线程池最大线程数N”的比例关系(治标不治本)。

示例代码:

netty-study工程com.zhangyiwen.study.bio.multi_thread_server包。

2、NIO多路复用 + Reactor模式

Reactor模式思想:分而治之+事件驱动

1)分而治之

一个connection里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。

Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。

2)事件驱动

每个Task对应一个特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Handler执行。

Reactor模型图解:

1)single reactor thread

Linux IO模型与Java网络编程_第7张图片
2)multi reactor threads

采用多个Reactor,每个Reactor在自己单独线程中执行,可以并行响应多个客户端的请求事件;

Netty采用类似这种模式,boss线程池就是多个mainReactor,worker线程池就是多个subReactor

Linux IO模型与Java网络编程_第8张图片
模型特点:
1)NIO中Channel是全双工的,Channel比流可以更好地映射底层操作系统的API(UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作);

2)客户端发起的连接操作是异步的,不需要像之前的客户端那样被同步阻塞;

3)JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,使得一个Reactor线程可以同时处理成千上万个客户端连接,而且性能不会随客户端连接的增加而线性下降,适合做高性能高负载的网络服务器方案。

示例代码:

netty-study工程com.zhangyiwen.study.nio.reactor_demo包。


四、常见的客户端模型

1、阻塞的Send/Recv模式

模型优点:
使用Send/Recv编写简单,适合于同步接口的封装。
模型缺点:
不适合做成异步接口,在Recv的时候该线程不能处理其它业务。

2、多路复用模式

模型优点:
1)既可封装成同步接口,也可以封装成异步CallBack接口,扩展性更强。
2)异步CallBack接口 可以进行多个请求发送,有数据可Recv时才处理。

五、客户端库的并行调用

1、阻塞Send/Recv模式+线程池

模型优点:
线程池使用比较方便。
模型缺点:
依赖线程的数量,设置太少,并发处理能力太弱,设置太多,线程切换频繁。

2、NIO多路复用+连接池

模型优点:
可以避免频繁的线程切换。
模型缺点:
如果官方的库没有提供这种功能,就只能自己去解析协议,没有线程池使用起来方便快捷。

3、连接复用

模型优点:
一般我们自定义的二进制协议,协议设计时都会带有一个唯一的序列号,Rsp通过这个序列号来找到对应哪个Req。
这样就可以复用一条链接进行多次请求发送和响应接收,无需使用线程池和连接池方式了。

你可能感兴趣的:(计算机网络)