概述
-
socket 将网络中的通信抽象成 I/O 操作, 可以对 socket 像文件一样的打开, 读写和关闭。
-
I/O 模型一般指磁盘 I/O 和网络 I/O
-
I/O 模型共有五种,分为两类
- 同步
- 阻塞式 I/O (blocking I/O)
- 非阻塞式 I/O (non - blocking I/O)
- I/O多路复用(multiplexing I/O)
- 信号驱动式 I/O (signal - driven I/O)
- 异步
- 异步 I/O (asynchronous I/O)
- 同步
-
对 I/O 的一次访问一般包括两个阶段
- 等待数据准备好(等待数据到达或等待数据复制到系统内核缓冲区)
- 将数据从内核缓冲区复制到应用进程缓冲区
同步阻塞 I/O
在JDK1.4之前,Java中主要是用的就是阻塞IO,叫做BIO
发起 IO 请求后进程被阻塞, 直到数据从内核缓冲区复制到应用进程缓冲区为止。
同步非阻塞 I/O
JDK1.4后提供了 NIO, 即同步非阻塞 I/O
发起 IO 请求后, 系统内部会以非阻塞的形式来执行 I/O , 即如果底层数据没有准备就绪, 那么内核返回异常信息, 之后接着继续不断的执行系统调用来获知 I/O 是否完成, 这种方式也称轮询(polling)
非阻塞只是指内部在不断轮询,不会被阻塞。同步是指在调用了NIO接口后, 还是需要一直同步等待,
I/O 多路复用
调用 select 或者 poll 等待多个socket, 能够实现对多个 IO 端口的监听, 其中任意一个socket准备好了, 就能返回可读, 之后再调用recvform系统调用, 将数据由内核态拷贝到用户进程, 这个过程是阻塞的。
信号驱动 I/O
应用进程使用sigaction系统调用, 内核立即返回, 应用进程可继续执行, 内核等到数据到达时向应用程序发送SIGIO信号, 应用进程收到信号后调用recvfrom将数据从内核复制到应用进程中
异步 I/O
jdk1.7之后支持AIO, 即异步IO模型。
应用进程执行 aio_read 系统调用后会立即返回, 进程可继续运行, 不会被阻塞, 内核在所有操作完成后会向应用进程发送信号。
五大 I/O 模型比较
- 同步和异步: 主要区别在第二阶段应用进程是否会阻塞, 在调用具体的JDK方法时, 同步不会立即返回, 而异步会直接返回继续执行。
- 同步的阻塞和同步的非阻塞: 指第一阶段是否会阻塞, 而第二阶段都会阻塞.
select poll 和 epoll
select poll 和 epoll都是 I/O 多路复用的具体实现,先出现的是select 之后是 poll 和 epoll。
I/O 多路复用监视多个描述符,当某个描述符准备就绪, 就通知程序进行相应的读写操作。
select
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- fd_set 由数组实现, 数组大小有限制, 无法监听大量的描述符.
- timeout 为超时参数, 调用 select 会一直等待到有描述符事件或者时间超时
select 每次轮询 fd 文件描述符检查就绪时间
缺点
- 每次调用都需要将fd拷贝到内核, 开销大
- 每次都需要遍历fd
- select 支持的文件描述符太小, 默认是1024.
poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
poll 和 select 的功能基本相同, 只是描述fd集合的方式不同, poll使用pollfd结构, 从而解决了单独线程能够打开文件描述符的数量有限制的问题.
epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll 不需要通过轮询来获得世界完成的描述符,已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理, epoll 不断轮询就绪链表, 避免了将描述符从进程缓冲区向内核缓存区的重复拷贝.
- epoll的默认工作模式为LT边缘触发: 已通知的事件如果未被处理则下次依然被通知
- ET模式水平触发: 已通知的事件必须被处理并且不再通知此事件
应用场景
- select
select 的 timeout 精度为1ns, 而 poll 和 epoll 为 1ms, 因此 select 适合实时性高的场景
select 的可移植性更好, 几乎被所有主流平台支持.
- poll
如果有大量描述符, 且对实时性要求不高的场景, 应该使用 poll 而不是 select
- epoll
仅适用于 linux
有大量的描述符需要同时轮询, 且连接最好是长连接。