上两节我们提到了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
非阻塞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操作彻底完成,因此是非阻塞的。(用户线程不因为I/O还未到达,一直傻傻的等待,而是需要不断去问内核是否有可用的数据。在准备就绪的情况下获取数据。)
补充一个gif(刷新查看):
I/O多路复用
非阻塞IO中需要用户线程在每个IO通路上,各自不断轮询IO状态,来判断是否有可处理的数据。如果把一个连接的可读可写事件剥离出来,使用单独的线程来对其进行管理。多个IO通路,都复用这个管理器来管理socket状态,这个叫I/O多路复用。
多路复用在内核中提供了select,poll,epoll三种方式:
select
原理示意
特点
只能处理有限(不同系统参数:1024/2048)个socket
select监控socket时不能准确告诉用户是哪个socket有可用数据,需要轮询判断
poll
原理示意
特点
与select没区别
采用链表实现,取消了文件个数的限制
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 上阅读请扫码关注【小眼睛聊技术】,欢迎与我交流,随时指出错误