I/O多路复用的简单操作及理解

简介: I/O多路复用


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。


三种方式:

  • select()

    • 底层为线性表
    • 跨平台通用
  • poll()

    • Linux Api, Windows中对应为WSAPoll()
    • 底层为线性表
  • epoll()

    • 底层为红黑树
    • Linux专属
    • 效率最高

做了什么

  • 将本该由程序员处理的缓冲区维护工作交给了内核,由内核监测缓冲区上的事件

select()

#include <sys/select.h>

struct timeval {
    time_t          tv_sec;  //秒
    suseconds_t     tv_usec; //微秒
};

itn select(int nfds, fd_set *reabufs, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
  • nfds:委托内核监测的文件描述符数量

    • 三个集合中最大的文件描述符+1 / 直接1024(上限值)
    • 内核需要线性遍历这些集合中的文件描述符,+1这个值是循环结束的条件
    • Windows中无效,指定为-1即可
  • readfds:内核只检测这个集合中文件描述符的读缓冲区

    • 传入传出参数
    • 常用
  • writefds:内核只检测这个集合中文件描述符对应的写缓冲区

    • 传入传出参数
    • 不常用,不需要使用的话可以指定为NULL
  • execptfds:内核只检测这个集合中文件描述符是否有异常状态

    • 传入传出参数
  • timeout:超时时长,用来强制接触select()的阻塞

    • NULL:检测不到就绪的文件描述符就一直阻塞
    • 固定秒数:一直检测不到就绪的文件描述符,指定时长后解除阻塞,返回0
    • 0:不等待,不阻塞
  • 返回值:

    • 大于0:成功,返回已就绪的文件描述符的个数
    • 等于-1:调用失败
    • 等于0:超时,没有检测到就绪的文件描述符

tips

  • fd_set是一个1024bit大小的东西,nfds的上限1024就是由此得来,fd_set拥有1042个标志位,每一个位都对应着0 / 1,代表着这个位对应的文件描述符的状态,由内核维护每一个位的状态

基于select处理服务器端并发的操作流程

  • 对fd_set标志位处理( FD_ZERO() \ FD_SET() )
  • 轮询检测指定fd( FD_ISSET() )

code

select_server

poll()

  • 不具备可移植性,性能又弱于epoll,一般不使用此函数

epoll()

概述

  • eventpoll
  • 高效:

    • 底层为红黑树
    • 使用回调机制而不是线性扫描,处理效率不会随着集合的变大而下降
    • 并没有使用共享内存
  • 没有最大文件描述符限制(取决于硬件)

函数

#include <sys/epoll.h>

// 创建epoll实例
int epoll_create(int size);
// 管理epoll红黑树(添加, 修改, 删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);

工作模式

水平模式

  • level trigger(LT)
  • 默认工作方式,支持blockno-block socket
  • 通知次数多,易于编写,但效率低
  • 读事件:

    • 文件描述符对应的读缓冲区还有数据,读事件就会被触发,epoll_wait()解除阻塞
    • 读缓冲区一次没有读完,读事件一直触发
    • 读数据是被被动的,必须通过读事件才知道有数据到达了,因此对于读事件的检测是必须
  • 写事件:

    • 如果文件描述符对应的写缓冲区可写,写事件就会被触发,epoll_wait()解除阻塞
    • 如果写缓冲区没有写满,写事件一直触发
    • 写数据是主动的,并且写缓冲区一般都是可写的(未满),所以对写事件的检测不是必须
    • code

    epoll_server_LT

边缘触发

  • edge trigger(ET)
  • 高速工作模式,只支持no-block socket
  • 通知次数少(只有新事件才会通知),编写较难,但效率高
  • 读事件:

    • 当读缓冲区有新的数据进入,读事件触发一次,没有新数据不会触发事件
    • 如果数据没有被全部取走,并且没有新数据进入,读时间不会再次出发,只通知一次
    • 如果数据被全部取走或只取走一部分,此时有新数据进入,读事件触发,并且只通知一次
  • 写事件:

    • 当写缓冲区可写,写事件只触发一次
    • 写缓冲区从不满到被写满,期间写事件只触发一次
    • 写缓冲区从满到不满,状态变为可写,写事件只会被触发一次
  • code

epoll_server_ET

ET模式下注意事项

  • 一个程序如果使用了ET模式,就应该使用非阻塞的fd,避免在read或write时使其中一个任务被"锁死"
  • 如何编程:

    • 使用非阻塞的fd
    • 在read或write返回EAGAIN时才进入epoll_wait的调用
  • EAGAIN:

    • nonblock fd read 返回 EAGAIN:缓冲区数据被读完
    • nonblock fd write 返回 EAGAIN:表示缓冲区被写满(待数据发送出去之后,缓冲重新进入可写状态,会触发EPOLLOUT事件,不会造成 epoll_wait被挂起)

SHOW ME THE CODE

  • 设置非阻塞fd
int cfd = accept(serverFd, NULL, NULL);\

int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
  • 设置ET模式
ev.events = EPOLLIN | EPOLLET;
  • 设置EAGIAN的判定
while (1) {
  int len = recv(fd, buf, sizeof(buf), 0);
  if (len == -1) {
    if (errno == EAGAIN) { // 判断是异常终止或数据接收完毕
      printf("数据接受完毕\n");
        break;
    }
    perror("recv error");
    exit(1);
  } else if (len == 0) {
    printf("客户端断开连接!\n");
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    break;
  }
}

参考

  1. Linux 教程
  2. epoll 相关问题简单说明
目录
相关文章
|
4月前
|
负载均衡 NoSQL 网络协议
网络中的阻塞与非阻塞以及reactor模型
网络中的阻塞与非阻塞以及reactor模型
19 0
|
24天前
|
Unix
poll 函数 I/O 多路复用的技术
【4月更文挑战第14天】poll 是另一种在各种 UNIX 系统上被广泛支持的 I/O 多路复用技术,虽然名声没有 select 那么响,能力一点不比 select 差,而且因为可以突破 select 文件描述符的个数限制,在高并发的场景下尤其占优势。
|
7月前
|
设计模式 网络协议 Java
Reactor 模式网络服务器【I/O多路复用】(C++实现)
Reactor 模式网络服务器【I/O多路复用】(C++实现)
261 0
|
8月前
|
监控
多路复用
多路复用
43 0
|
9月前
|
Linux
网络编程之阻塞与非阻塞的理解
网络编程之阻塞与非阻塞的理解
71 0
|
10月前
|
NoSQL Redis
I/O多路复用模型?
I/O多路复用模型?
37 0
|
存储 Linux
I/O 多路复用:select/poll/epoll 实现原理及区别
I/O 多路复用:select/poll/epoll 实现原理及区别
187 0
|
Linux
Linux网络编程之阻塞与非阻塞
Linux网络编程之阻塞与非阻塞
167 0
|
网络协议 编译器 测试技术
管道阻塞的机制|学习笔记
快速学习管道阻塞的机制
109 0
管道阻塞的机制|学习笔记
|
存储 Java Linux
什么是BIO,NIO?他们和多路复用器有啥关系?
阿粉第一次了解到io相关知识是在网上看面经的时候,平时只会写业务代码,面对bio,nio,多路复用器这些概念简直是一头雾水。 当阿粉尝试单独去学习这些名词,发现很难学懂,如果能有一篇文章串起来讲讲他们的关系,可能对初学者来说有一定的帮助,所以便有了下面这篇文章。