Java基础相关 | BIO/NIO
部分图文转自B站小刘老师
1.BIO
1.1 BIO通信过程
如下系列图:为BIO情况下一个进程阻塞后的通讯过程
对应的读操作,是阻塞操作,如果缓冲区有数据才可读成功,没数据直接阻塞!
此时进程A被阻塞,所以进程A出队
进程A进入到客户端的等待队列中(等待读缓冲区有数据)
此时只剩下三个进程在运行队列
此时通过外界传入一些数据的话,通过DMA写入RAM中
CPU此时收到硬件的中断信号
cpu将当前的B进程从用户态转为内核态(响应中断)
B进程的响应中断:
- 保存进程用户态堆栈信息到进程描述符
- 修改CPU寄存器,将堆栈指针指向当前进程内核态堆栈(切换到内核态)
- 根据IRQ向量到向量表中查询合适的中断处理程序
- 执行网卡中断处理程序
网卡中断处理程序:
言归正题,此时进程A已经有数据,恢复到运行队列的队尾!对应进程的状态转变为“待运行”状态,也就是说再次有机会获得CPU执行权!!
1.2 BIO的缺点
- 一个进程只能监听一个Socket
- 使用BIO较难实现C10K, C100K等需求 (10K个客户端或者100K个客户端的需求,java在linux中的一个线程相当于一个进程)
2.NIO
2.1 多路复用函数
2.1.1 linux select函数
在Linux中,我们可以使用select函数实现I/O端口的复用,传递给select函数的参数会告诉内核:
- 我们所关心的文件描述符
- 对每个描述符,我们所关心的状态。
- 我们要等待多长时间(阻塞时长,可规定)
从select函数返回后,内核告诉我们一下信息:
- 对我们的要求已经做好准备的描述符的个数
- 对于三种条件哪些描述符已经做好准备.(读,写,异常)
有了这些返回信息,我们可以调用合适的I/O函数(通常是read或write),并且这些函数不会再阻塞.
2.1.1.1 select函数接口
Linux select函数接口
#include <sys/select.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
fd_set 结构:
是bitmap(二进制),长度为1024位
返回:做好准备的文件描述符的个数,超时为0,错误为-1
首先我们先看一下最后一个参数。它指明我们要等待的时间:
struct timeval {
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
}
有三种情况:
timeout == NULL
:等待无限长的时间timeout->tv_sec == 0 && timeout->tv_usec == 0
:不等待,直接返回。(非阻塞)timeout->tv_sec !=0 || timeout->tv_usec != 0
:等待指定的时间。
中间的三个参数readset, writset, exceptset,指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在fd_set类型中,fd_set其实就是位图!
2.1.1.2 select 原理图
伪代码:
内核态rset拷贝到用户态之中,切转换回用户态
2.1.2 epoll
epoll优化了数据从用户到内核再由内核到用户反复拷贝的问题,具体方案:在内存开辟一块名为epoll的空间,存储需要拷贝的数据
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和pol来说,epoll更加灵活,没有描述符限制。epal使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
2.1.2.1 epoll接口
epoll操作过程需要三个接口,分别如下:
#include <sys/epoll.h>
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_create
函数是一个系统函数,函数将在内核空间内开辟一块新的空间,可以理解为epoll结构空间,返回值为epoll的文件描述符编号,方便后续操作使用。epoll_ctl
是epoll事件注册函数,epoll与select不同,select函数是调用时指定需要监听的描述符和事件,epoll先将用户感兴趣的描述符事件注册到epoll空间内,此函数是非阻塞函数,作用仅仅是增删改epoll空间内的描述符信息。-
参数一:
epfd
,很简单,epoll结构的进程fd编号,函数将依靠该编号找到对应的epoll结构。 -
参数二:
op
,表示当前请求类型,由三个宏定义(EPOLL CTL ADD:注册新的fd到epfd中)
(EPOLL CTL MOD:修改已经注册的fd的监听事件)
(EPOLLCTL DEL: 从epfd中删除一个fd) -
参数三:
fd
,需要监听的文件描述符。一般指socket_fd。 -
参数四:
event
,告诉内核对该fd资源感兴趣的事件。
-
2.1.2.2 epoll工作模式
epoll对文件描述符的操作有两种模式:LT(水平触发)和ET(边缘触发)。LT模式是默认模式,LT模式与ET模式的区别如下:
- LT(水平触发)∶事件就绪后,用户可以选择处理或者不处理,如果用户本次未处理,那么下次调用epoll wait时仍然会将未处理的事件打包给你
- ET(边缘触发)︰事件就绪后,用户必须处理,因为内核不给你兜底了,内核把就绪的事件打包给你后,就把对应的就绪事件清理掉了。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。
2.1.2.3 epoll代码
2.1.2.4 epoll 原理图
伪代码
如果A没数据,则出队,进入epoll的等待队列;
此时客户端的等待队列不再是等待队列,而是直接指向epoll!
队列指向已经就绪
A恢复运行队列时, 将内核态堆栈的rdlist拷贝到用户态堆栈
2.1.3 select 和 epoll的优缺和区别
select缺点:
- 每次都需传入rset参数,切rset无法复用,每次都会复位
- 每次监听都需要将数据拷贝到内核中,内核就绪后也会拷贝回用户
- 需要全部遍历队列,找到就绪状态的再运行,效率低下
epoll优点:
- epoll通过一块公共空间解决了select的多次复制
- 已就绪的进程在一个集合中,不需要遍历未就绪的(减少了无序遍历)