BIO的问题
阻塞性能问题
即使多线程异步,每个连接占用一个线程资源。
ServerSocket ss = null;
try {
ss = new ServerSocket(port);
ExecutorService executor = Executors.newFixedThreadPool(2);
while(true) {
Socket incoming = ss.accept();
executor.submit(new ClientTask(incoming));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
safeClose(ss);
}
当连接已经建立成功,服务端和客户端都会拥有一个 Socket 实例;
每个 Socket 实例都有一个 InputStream 和 OutputStream,通过这两个对象来交换数据。
网络 I/O 都是以字节流传输的。当 Socket 对象创建时,操作系统将会为 InputStream 和 OutputStream 分别分配一定大小的缓冲区,数据的写入和读取都是通过这个缓存区完成的。写入端将数据写到 OutputStream 对应的 SendQ 队列中,当队列填满时,数据将被发送到另一端 InputStream 的 RecvQ 队列中,如果这时 RecvQ 已经满了,那么 OutputStream 的 write 方法将会阻塞直到 RecvQ 队列有足够的空间容纳 SendQ 发送的数据。值得特别注意的是,这个缓存区的大小以及写入端的速度和读取端的速度非常影响这个连接的数据传输效率,由于可能会发生阻塞,所以网络 I/O 与磁盘 I/O 在数据的写入和读取还要有一个协调的过程,如果两边同时传送数据时可能会产生死锁
伪异步IO的问题
见代码 SocketDemo.java
image.png
Unix I/O模型
https://blog.csdn.net/woaixiaopangniu521/article/details/70279143
https://blog.csdn.net/baiye_xing/article/details/74331041
1. 同步阻塞IO模型:
同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。
当用户进程进行系统调用时,内核就开始了I/O的第一个阶段,准备数据到缓冲区中,当数据都准备完成后,则将数据从内核缓冲区中拷贝到用户进程的内存中,这时用户进程才解除block的状态重新运行。
- 能够及时返回数据,无延迟
-
性能下降
同步阻塞IO模型
2. 同步非阻塞IO:
- 拷贝数据的整个过程,进程仍然是阻塞的
- 需要不断询问数据是否准备好了
- 能够在等待任务完成的过程中处理其他事件
- 由于需要轮询,所以延迟会增加
3. IO多路复用
首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
IO多路复用伪代码描述:
//注册 可以注册多个
select(socket);
while(1) {
sockets = select();
for(socket in sockets) {
if(can_read(socket)) {
read(socket, buffer);
process(buffer);
}
}
}
4. 信号驱动I/O模型(signal driven I/O, SIGIO)
信号驱动I/O模型从上图可以看出,只有在I/O执行的第二阶段阻塞了用户进程,而在第一阶段是没有阻塞的。该模型在I/O执行的第一阶段,当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
5. 异步I/O模型(AIO, asynchronous I/O)
image.png异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数)
伪代码描述:
void UserCompletionHandler::handle_event(buffer) {
process(buffer);
}
{
aio_read(socket, new UserCompletionHandler);
}
Reactor和Proactor模式的主要区别:https://www.cnblogs.com/pigerhan/p/3474217.html
https://tech.meituan.com/nio.html
参考:
网友评论