JAVA_IO-select poIl epoll

        多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

select

poll

epoll

操作方式

遍历

遍历

回调

数据结构

bitmap

数组

红黑树

最大连接数

1024(x86)或2048 (x64)

无上限

无上限

最大支持文件描述符数一般有最大值限制

65535

65535

fd拷贝

每次调用select,都需 要把fd集合从用户态搏 贝到内核态

每次调用poll,都需要 把fd集合从用户态拷贝 到内核态

fd首次调用epol_ctl拷贝,每次调用 epollwait不拷贝

工作效率

每次调用都进行线性遍 历,时间复杂度为 o(n)

每次调用都进行线性遍 历,时间复杂度为 (n)

事件通知方式,每当fd就绪,系统注册的 回调函数就会被调用,将就绪fd放到 readyList里面,时间复杂度O(1)

select

官网

Linux官网或者man select(2) - Linux manual page

select是第一个实现(1983左右在BSD里面实现)

优点

select其实就是把NIO中用户态要遍历的fd数组(我们的每一个socket链接,安装进ArrayList!里面的那个)拷贝到了内核态,让内核态来遍历,因为用户态判断socket是否有数据还是要调用内核态的,所有拷贝到内核态后,这样遍历判断的时候就不用一直用户态和内核态频繁切换了

问题

  1. bitmapi最大1024位,一个进程最多只能处理1024个客户端
  2. &rset不可重用,每次socket有数据就相应的位会被置位
  3. 文件描述符数组拷贝到了内核态(只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)),仍然有开销
  4. select调用需要传入fd数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
  5. select并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历。select仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历

C语言代码

// 创建套接字
socket(AF_INET, SOCK_STREAM, 0);
int sockfd; // 注意声明sockfd

// 清零addr结构体
memset(&addr, 0, sizeof(addr));

// 设置地址族
addr.sin_family = AF_INET; // 使用等号赋值

// 设置端口
addr.sin_port = htons(2000); // 使用等号赋值

// 设置IP地址为任意地址
addr.sin_addr.s_addr = INADDR_ANY; // 使用等号赋值

// 绑定套接字
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

// 开始监听
listen(sockfd, 5);

// 模拟5个客户端连接
for (int i = 0; i < 5; i++) { // 初始化i,使用大括号包裹循环体
    // 重置client结构体
    memset(&client, 0, sizeof(client));

    // 准备接收客户端地址长度
    socklen_t addrlen = sizeof(client);
    fds[i]=accept(sockfd,(struct sockaddr*)&client,&addrlen);
    if(fds[i]>max) max fds[i];
}

select方式,既做到了一个线程处理多个客户端连接(文件描述符),又减少了系统调用的开销(多个文件描述符只有一次select的系统调用+N次就绪状态的文件描述符的read系统调用

poIl

优点

  1. poll使用pollfd数组来代替select中的bitmap,数组没有1024的限制,可以一次管理更多的client。它和select的主要区别就是,去掉了select只能监听1024个文件描述符的限制。
  2. 当pollfds数组中有事件发生,相应的revents置位为1,遍历的时候又置位回零,实现了pollfd数组的重用

问题

poll解决了select缺点中的前两条,其本质原理还是select的方法,还存在select中原来的问题

1、pollfds数组拷贝到了内核态,仍然有开销

2、poll并没有通知用户态哪一个socket有数据,仍然需要O(n)的遍历

Linux官网或者man

1997年实现了poll

C语言代码

pol的执行流程:

1.将五个fd从用户态烤贝到内核态

2.poll为阻塞方法,执行pol方法,如果有数据会将fd对应的revents置为POLLIN

3.pol方法返回

4.循环遍历,查找哪个fd被置位为POLL山N了

5.将reventsi重置为0便于复用

6.对置位的fd进行读取和处理

解决的问题:

1.解决了bitmap大小限制

2.解决了rset不可重用的情况

后面由于二者原理相同,所以没能解决

 epoll

事件通知机制

  1. 当有网卡上有数据到达了,首先会放到DMA(内存中的一个bufr,网卡可以直接访问这个数区域)
  2. 网卡向cpu发起中断,让cpu先处理网卡的事
  3. 中断号在内存中会绑定一个回调,哪个socket中有数据,回调函数就把哪个socket放入就绪链表中

三步调用

epoll_create

创建一个epoll句柄

int epoll_create(int size)

epoll_ctl

向内核添加、修改或删除要监控的文件描述符

epoll_wait

类似发起了select()调用

Linux官网或者man

epoll(7) - Linux manual page

在2002年被神Davide Libenzi(戴维德.利本兹)发明出来了

C语言代码

结论

  1. 多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的while循环里多次系统调用,变成了一次系统调用+内核层遍历这些文件描述符。
  2. epoll是现在最先进的IO多路复用器,Redis、Nginx,.linux中的Java NIO都使用的是epoll。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
  3. 一个socket的生命周期中只有一次从用户态拷贝到内核态的过程,开销小
  4. 使用event事件通知机制,每次socket中有数据会主动通知内核,并加入到就绪链表中,不需要遍历所有的socket
  5. 在多路复用IO模型中,会有一个内核线程不断地去轮询多个socket的状态,只有当真正读写事件发送时,才真正调用实际的IO读写操作。因为在多路复用引O模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只真正有读写事件进行时,才会使用IO资源,所以它大大减少来资源占用。多路IO复用模型是利用select、pol、epoll可以同时监察多个流的I/O事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有IO事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。采用多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈

为什么3个都保有

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值