Java基础相关 | BIO/NIO/Select/Epoll

Java基础相关 | BIO/NIO

部分图文转自B站小刘老师

1.BIO

1.1 BIO通信过程

如下系列图:为BIO情况下一个进程阻塞后的通讯过程
在这里插入图片描述
对应的读操作,是阻塞操作,如果缓冲区有数据才可读成功,没数据直接阻塞!
在这里插入图片描述


此时进程A被阻塞,所以进程A出队
在这里插入图片描述
进程A进入到客户端的等待队列中(等待读缓冲区有数据)

在这里插入图片描述


此时只剩下三个进程在运行队列
在这里插入图片描述

此时通过外界传入一些数据的话,通过DMA写入RAM中
在这里插入图片描述


在这里插入图片描述
CPU此时收到硬件的中断信号
在这里插入图片描述
cpu将当前的B进程从用户态转为内核态(响应中断)

B进程的响应中断:

  1. 保存进程用户态堆栈信息到进程描述符
  2. 修改CPU寄存器,将堆栈指针指向当前进程内核态堆栈(切换到内核态)
  3. 根据IRQ向量到向量表中查询合适的中断处理程序
  4. 执行网卡中断处理程序
    在这里插入图片描述

网卡中断处理程序:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


言归正题,此时进程A已经有数据,恢复到运行队列的队尾!对应进程的状态转变为“待运行”状态,也就是说再次有机会获得CPU执行权!!
在这里插入图片描述

在这里插入图片描述

1.2 BIO的缺点

  1. 一个进程只能监听一个Socket
  2. 使用BIO较难实现C10K, C100K等需求 (10K个客户端或者100K个客户端的需求,java在linux中的一个线程相当于一个进程)

2.NIO

2.1 多路复用函数

2.1.1 linux select函数

在Linux中,我们可以使用select函数实现I/O端口的复用,传递给select函数的参数会告诉内核:

  1. 我们所关心的文件描述符
  2. 对每个描述符,我们所关心的状态。
  3. 我们要等待多长时间(阻塞时长,可规定)

从select函数返回后,内核告诉我们一下信息:

  1. 对我们的要求已经做好准备的描述符的个数
  2. 对于三种条件哪些描述符已经做好准备.(读,写,异常)

有了这些返回信息,我们可以调用合适的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; /*微秒*/
}

有三种情况:

  1. timeout == NULL :等待无限长的时间
  2. timeout->tv_sec == 0 && timeout->tv_usec == 0 :不等待,直接返回。(非阻塞)
  3. 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);
  1. epoll_create 函数是一个系统函数,函数将在内核空间内开辟一块新的空间,可以理解为epoll结构空间,返回值为epoll的文件描述符编号,方便后续操作使用。
  2. epoll_ctl 是epoll事件注册函数,epoll与select不同,select函数是调用时指定需要监听的描述符和事件,epoll先将用户感兴趣的描述符事件注册到epoll空间内,此函数是非阻塞函数,作用仅仅是增删改epoll空间内的描述符信息。
    1. 参数一: epfd,很简单,epoll结构的进程fd编号,函数将依靠该编号找到对应的epoll结构。

    2. 参数二:op,表示当前请求类型,由三个宏定义

      (EPOLL CTL ADD:注册新的fd到epfd中)
      (EPOLL CTL MOD:修改已经注册的fd的监听事件)
      (EPOLLCTL DEL: 从epfd中删除一个fd)

    3. 参数三: fd,需要监听的文件描述符。一般指socket_fd。

    4. 参数四: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缺点

  1. 每次都需传入rset参数,切rset无法复用,每次都会复位
  2. 每次监听都需要将数据拷贝到内核中,内核就绪后也会拷贝回用户
  3. 需要全部遍历队列,找到就绪状态的再运行,效率低下

epoll优点:

  1. epoll通过一块公共空间解决了select的多次复制
  2. 已就绪的进程在一个集合中,不需要遍历未就绪的(减少了无序遍历)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值