上两节我们提到了 select 和 poll 函数,查看 man 手册:
SELECT(2) Linux Programmer's Manual SELECT(2)
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
复制代码
POLL(2) Linux Programmer's Manual POLL(2)
NAME
poll, ppoll - wait for some event on a file descriptor
SYNOPSIS
#include <poll.h>
复制代码
synchronous I/O multiplexing 中文解释是同步的多路复用,因此 select 是一个同步的 I/O 多路复用模式。Unix 共五种 I/O 模型:
阻塞 I/O
非阻塞 I/O
I/O 多路复用
信号驱动
异步 I/O
信号驱动和真正的异步 I/O 并不常用,我们重点说一下前三个。
阻塞和非阻塞的概念描述的是用户线程调用内核 IO 操作的方式:阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,无需等到 IO 操作彻底完成
阻塞 I/O
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept();
System.out.println("链接端口:" + socket.getPort());
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String str = null;
while ((str = reader.readLine()) != null) {
System.out.println("接受:" + str);
socket.getOutputStream().write("ok\n".getBytes());
socket.getOutputStream().flush();
if ("over".equals(str)) {
System.out.println("要关闭了");
socket.close();
break;
}
}
System.out.println("===========");
}
复制代码
当有数据获取时,用户线程要释放 cpu,直到数据由内核处理完成,整个过程用户线程是阻塞的。
阻塞IO
用户线程调用内核 IO 操作,需要等 IO 彻底完成后才返回到用户空间,因此是阻塞 IO
非阻塞 I/O
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
System.out.println("没有链接 ");
continue;
}
System.out.printf("新链接,端口是 %s", ((InetSocketAddress) socketChannel.
getRemoteAddress()).getPort());
ByteBuffer ds = ByteBuffer.allocate(10);
socketChannel.read(ds);
System.out.println("接受数据");
}
复制代码
非阻塞IO
IO 操作被调用后立即返回给用户一个状态值,无需等到 IO 操作彻底完成,因此是非阻塞的。(用户线程不因为 I/O 还未到达,一直傻傻的等待,而是需要不断去问内核是否有可用的数据。在准备就绪的情况下获取数据。)
补充一个 gif(刷新查看):
I/O 多路复用
非阻塞 IO 中需要用户线程在每个 IO 通路上,各自不断轮询 IO 状态,来判断是否有可处理的数据。如果把一个连接的可读可写事件剥离出来,使用单独的线程来对其进行管理。多个 IO 通路,都复用这个管理器来管理 socket 状态,这个叫 I/O 多路复用。
多路复用
多路复用在内核中提供了 select,poll,epoll 三种方式:
select
原理示意
select
特点
只能处理有限(不同系统参数:1024/2048)个 socket
select 监控 socket 时不能准确告诉用户是哪个 socket 有可用数据,需要轮询判断
poll
原理示意
poll
特点
与 select 没区别
采用链表实现,取消了文件个数的限制
epoll
原理示意
epoll
epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了
fd 上的事件发生时,与它对应的回调函数就会被调用把 fd 加入链表,其他处于“空闲的”状态的则不会被加入
epoll 从上面链表中获取有事件发生的 fd
epoll 准确的表述应该是 I/O 事件通知器:
EPOLL(7) Linux Programmer's Manual EPOLL(7)
NAME
epoll - I/O event notification facility
SYNOPSIS
#include <sys/epoll.h>
复制代码
特点
没有最大连接限制
可以直接告诉用户程序哪一个,哪个连接有数据了
epoll 详细的介绍可以参考我的专辑 http://mp.weixin.qq.com/mp/homepage?__biz=MzI3MDYwOTYwOA==&hid=2&sn=b7d5d6aa0b9f18c382a97f146c02b540&scene=18#wechat_redirect 中的第二篇文章《读懂才会用:Redis 源码系列》
同步异步的概念
同步和异步的概念描述的是用户线程与内核的交互方式:
同步是指用户线程发起 IO 请求后需要等待或者轮询内核 IO 操作完成后才能继续执行;异步是指用户线程发起 IO 请求 后仍继续执行,当内核 IO 操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
因此 阻塞 I/O,非阻塞 I/O,I/O 多路复用,都属于同步调用。只有实现了特殊 API 的 AIO 才是异步调用,之后单开一篇讲解。
AIO(7) Linux Programmer's Manual AIO(7)
NAME
aio - POSIX asynchronous I/O overview
DESCRIPTION
The POSIX asynchronous I/O (AIO) interface allows applications to initiate one or more I/O operations that are performed asyn‐
chronously (i.e., in the background). The application can elect to be notified of completion of the I/O operation in a variety of
ways: by delivery of a signal, by instantiation of a thread, or no notification at all.
复制代码
系列
NIO 看破也说破(一)—— Linux/IO 基础
NIO 看破也说破(二)—— Java 中的两种 BIO
NIO 看破也说破(三)—— 不同的 IO 模型
NIO 看破也说破(四)—— Java 的 NIO
NIO 看破也说破(五): 搞,今天就搞,搞懂Buffer
关注我
如果您在微信阅读,请您点击链接 关注我 ,如果您在 PC 上阅读请扫码关注我,欢迎与我交流随时指出错误
评论 (9 条评论)