IO
阻塞IO
以server处理socket连接为例, 标准的几步
- socket
- bind
- listen
- accept <- block
- receive
不管是Java还是c, 在accept时都是阻塞等待操作系统返回新连接
非阻塞IO
- socket
- bind
- listen
- fcntl(O_NONBLOCK), accept <- unblock
- receive
手动设置fd(file_descriptor)的属性, 可以使用操作系统提供的非阻塞接口, 即无输入返回0, 有输入返回大于0, 如果需要处理数据, 需要循环检查返回值
接口立即返回, 程序可以执行下文, 所以是非阻塞
要等接口返回有效内容后才能执行期望的操作, 所以是同步的
IO多路复用
上面两种模型, 都是一个线程对应一个连接
在Java中创建线程,一个线程默认就会预留1M的空间,那么1G的内存也不过只能支持1000个线程创建而已。而且,随着线程数的增多,线程之间的调度和切换,引发的资源竞争也会加剧,使整个系统变得很慢
IO多路复用, 支持一个线程管理多个连接, 所有连接均没有消息时, 对应的接口即阻塞
select
- socket
- bind
- listen
- select <- block
- accept
- receive
1 | while(true) { |
poll
- socket
- bind
- listen
- poll <- block
- accept
- receive
和select基本相同, 只是fd_set从数组换成了链表, 支持的fd数超过1024
epoll
对poll的优化
- 返回可读的fd, 不需要再遍历一遍哪些可读
- 接口拆分为 epoll_ctl 和 epoll_wait, 减少了用户空间到内核空间的拷贝
只有较新的linux支持epoll
两种模式
Edge模式
空的接收缓冲区刚接收到数据时触发读事件; 满的缓冲区刚空出空间时触发读事件
Level模式
接收缓冲区不为空, 有数据可读, 则读事件一直触发; 发送缓冲区不满可以继续写入数据, 则写事件一直触发
netty
Java NIO的selector是对select/poll/epoll的封装
感想
看讲Java IO的文章, 总是不理解为什么会有这么多模型, 既然有效率更高的, 直接用更好的不就行了吗?
所以说问题要看本质, Java IO能做什么, 取决于操作系统提供了什么接口
说到底还是大学课堂涉及的内容, 上课时不知道学了能干啥而知其然不知其所以然, 现在遇到了使用场景就得得好好补一补