Linux IO多路复用--基础

一、IO多路复用 -- select()

结构体

  • fd_set:描述符集合(long类型数组)

函数

  • FD_ZERO():清空描述符集合
  • FD_SET():设置监听的描述符(把监听的描述符设置为1)
  • FD_ISSET():判断描述符是否设置(判断描述符是否设置为1)
  • FD_CLR() :清除监听的描述符(把监听的描述符设置为0)
  • select():监听描述符事件,如果描述符集合中没有就绪,等待;反之,函数返回,把描述符集合清空,并设置已经就绪的描述符(设置为1)。

就绪条件

Linux IO多路复用--基础_第1张图片

编码流程

  1. 定义描述符集
  2. 清空描述符集
  3. 设置指定的描述符并获取最大的描述符值+1
  4. 等待描述符就绪
  5. 判断已就绪的描述符,并做对应处理。

代码结构

// 定义描述符集
fd_set rset;

// 清空描述符集
FD_ZERO(&rset);

// 设置描述符 
FD_SET(fd1,&rset);
FD_SET(fd2,&rset);

// 获取最大描述符+1
int maxfdp1 = max(fd1,fd2) + 1;

// 等待描述符就绪
if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){

    // 判断已就绪的描述符
    if(FD_ISSET(fd1,&rset)){
            // do somthing
    }
    if(FD_ISSET(fd2,&rset)){
            // do somthing
    }
}

注意

  1. fd_set可容纳最大描述符数为FD_SETSIZE
  2. 每一次select()前,必须重新设置描述符,如果设置了新的描述符,需要重新计算maxfdp1

二、IO多路复用 -- poll()

结构体

  • struct pollfd
成员 含义
fd 描述符
events 监听事件,主要用于设置监听事件
revents 实际触发的事件,用于判断触发的事件

函数

  • poll()

编码流程

  1. 定义pollfd结构体数组
  2. 初始化pollfd结构体数组
  3. 设置监听poll事件
  4. 等待poll事件
  5. 判断触发事件的描述符,并做对应处理。

触发事件

Linux IO多路复用--基础_第2张图片

代码结构

// 定义pollfd结构体数组
struct pollfd pollfds[OPEN_MAX];

// 初始化pollfd结构体数组
int i;
for(i=0;i0){
    int i;
    for(i=0;i

注意

struct pollfd数组的最大数是OPEN_MAX(或者linux/fs.h中的INR_OPEN_MAX )
还可以通过如下方式查看:

  • cat /proc/sys/fs/file-max
  • ulimit

IO多路复用 -- epoll()

结构体

struct epoll_event

成员 含义
data.fd 描述符
events 设置/获取epoll事件

函数

  • epoll_create():创建epoll描述符
  • epoll_ctl():控制epoll描述符
  • epoll_wait():等待epoll描述符

结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,定义如下:

    typedef union epoll_data {
        void *ptr;
         int fd;
         __uint32_t u32;
         __uint64_t u64;
     } epoll_data_t;//保存触发事件的某个文件描述符相关的数据
     struct epoll_event {
         __uint32_t events;      /* epoll event */
         epoll_data_t data;      /* User data variable */
     };

其中events表示感兴趣的事件和被触发的事件,可能的取值为:

事件 含义
EPOLLIN 文件描述符可读
EPOLLOUT 文件描述符可写
EPOLLPRI 文件描述符高优先级可读
EPOLLERR 文件描述符发生错误
EPOLLHUP 文件描述符被挂断
EPOLLET ET的epoll工作模式

编码流程

  1. 创建epoll描述符
  2. 注册epoll事件
  3. 等待epoll事件
  4. 判断触发epoll事件的描述符和事件
  5. 关闭epoll描述符

触发条件

  • ET(Edge Triggered)模式--边沿触发
操作 触发条件
接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件
  • LT(Level Triggered)模式--水平触发
操作 触发条件
接收缓冲区不为空,有数据可读,读事件一直触发
发送缓冲区不满,可以继续写入数据,写事件一直触发

代码结构

  • LT
// 创建epoll描述符
int epollfd = epoll_create(OPEN_MAX);

// 注册epoll事件
struct epoll_event evt;
evt.data.fd = fd1;
evt.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd1,&evt);
evt.data.fd = fd2;
evt.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd2,&evt);

// 等待epoll事件
int evts_cnt = 2;
struct epoll_event evts[evts_cnt];
int fd_cnt = epoll_wait(epollfd,&evts,evts_cnt,-1);
int i;
for(i=0;i
  • ET

ET模式的文件句柄必须是非阻塞的。

// 创建epoll描述符
int epollfd = epoll_create(OPEN_MAX);

// 注册epoll事件
struct epoll_event evt;
evt.data.fd = fd1;
fcntl(fd1,F_SETFL,O_NONBLOCK);// fd1必须是非阻塞的
evt.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd1,&evt);
evt.data.fd = fd2;
fcntl(fd2,F_SETFL,O_NONBLOCK);// fd2必须是非阻塞的
evt.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd2,&evt);

// 等待epoll事件
int evts_cnt = 2;
struct epoll_event evts[evts_cnt];
int fd_cnt = epoll_wait(epollfd,&evts,evts_cnt,-1);
int i;
for(i=0;i

比较

select IO多路复用

  • 缺点
    • 只能监视FD_SETSIZE个连接
    • 不能确切指定有数据的socket
    • 每次需要修改传入的fd_set
    • 线程不安全

poll IO多路复用

  • 优点

    • 不需要不修改传入的pollfd数组
    • 可以监视任意个连接
  • 缺点

    • 不能确切指定有数据的socket
    • 线程不安全

epoll IO多路复用

  • 优点
    • 能确切指定有数据的socket
    • 线程安全

你可能感兴趣的:(Linux IO多路复用--基础)