Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

IO模型

普通模型

  • 阻塞IO

    • 在进行recvfrom系统调用、等待数据准备的过程中,进程将会堵塞,直到数据准备好后函数返回
    • 此模型下,进程将休眠等待
  • 非阻塞IO

    • 在进行recvfrom系统调用时,如果数据没有准备好,函数将直接返回EWOULDBLOCK错误
    • 此模型下,需要定期调用recvfrom函数

复用模型

  • select函数

    • 传入想要监听的文件描述符位数组,通过select系统调用得到可读取的位数组,并逐一检查,然后通过recvfrom函数读取
    • 缺点
      • 每次调用select后,都需要循环检查包含所有文件描述符的位数组,找到值为1的进行处理
      • 每次调用select时,都要通过“构造或拷贝”传入监视对象信息,这一项是最大的性能弱点
    • 原型
      #include <sys/select.h>
      #include <sys/time.h>
      
      int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
      
      • maxfd 监视的文件描述符fd的数量
        • 一般取 最大的fd值再加1
      • readset 注册所有关注“是否有待读取数据”的fd
        • 函数将修改此项,更新为“存在”待读取数据的fd
      • writeset 注册所有关注“是否可传输无阻塞数据”的fd
        • 函数将修改此项,更新为“可传输”无阻塞数据的fd
      • exceptset 用于注册“是否发生异常”的fd
        • 函数将修改此项,更新为“发生异常”的fd
      • timeout 为防止select函数陷入无限阻塞,需传递超时信息
        • 函数将修改此项,更新为返回时剩余的时间
        • 结构体定义如下
          struct timeval {
              long tv_sec; //seconds
              long tv_usec; //microseconds
          }
          
    • fd_set位数组
      • fd_set是一个位数组,每一位代表一个文件描述符fd
        • 调用select前,若某个位为1,代表此处fd是监视对象
        • 调用select后,若某个位为1,代表此处fd可读取/可传输/有异常
      • 操作位数组的宏
        FD_ZERO(fd_set* fdset); //全部置0
        FD_SET(int fd, fd_set* fdset); //注册某个fd,置1
        FD_CLR(int fd, fd_set* fdset); //清除某个fd的监视,置0
        FD_ISSET(int fd, fd_set* fdset); //判断某个fd值是否为1,用于验证调用结果
        
    • 例子select.c
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/select.h>
      #include <sys/time.h>
      #define BUF_SIZE 50
      
      int main(int argc, char* argv[]) {
          fd_set reads, temps;
          FD_ZERO(&reads);
          FD_SET(0, &reads); //0是标准输入
          struct timeval timeout;
          
          char buf[BUF_SIZE];
          int result, str_len;
      
          while (1) {
              temps = reads; //由于传入后会被修改,难以下次循环复用,所以每次都要传副本
              timeout.tv_sec = 5; //传入后也会被修改,所以每次都要重设
              timeout.tv_usec = 0; 
              result = select(1, &temps, 0, 0, &timeout);
              if (result == -1) {
                  puts("select() error");
                  break;
              } else if (result == 0) {
                  puts("timeout");
              } else {
                  if (FD_ISSET(0, &temps)) {
                      str_len = read(0, buf, BUF_SIZE);
                      buf[str_len] = 0;
                      printf("message from console: %s", buf);
                  }
              }
          }
      }
      
  • epoll函数

    • 仅Linux可用,内核版本2.5.44以上(含)才支持,在Window上要用另一种实现IOCP

    • 只要创建出保存文件描述符的空间,然后请求操作系统注册文件描述符,即可通过不断调用epoll_wait函数得到发生变化的文件描述符

    • 原型

      #include <sys/epoll.h>
      
      
      int epoll_create(int size);
      
      • 用于创建文件描述符保存空间
      • size —— 向操作系统提供的空间建议大小(在2.6.8内核之后此参数将失去作用)
      • 返回epoll空间的文件描述符
      int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
      
      • epfd —— 上面创建的文件描述符
      • op —— 操作行为
        • EPOLL_CTL_ADD —— 添加
        • EPOLL_CTL_DEL —— 移除
        • EPOLL_CTL_MOD —— 更改
      • fd —— 关注的文件描述符
      • event —— 是一个epoll_event结构体
        • 内部存放具体的事件events以及关注的文件描述符fd
        • 结构体定义
          struct epoll_event {
              __uint32_t events;
              epoll_data_t data;
          }
          
          typedef union epoll_data {
              void* ptr;
              int fd;
              __uint32_t u32;
              __uint64_t u64;
          }
          
        • events可选值
          • EPOLLIN —— 需要读取数据
          • EPOLLOUT —— 需要立即发送数据
          • EPOLLPRI —— 收到OBB紧急数据
          • EPOLLRDHUP —— 连接断开或半关闭
          • EPOLLERR —— 发生错误
          • EPOLLET —— 设置以边缘触发方式得到通知
          • EPOLLONESHOT —— 设置发生一次事件后,相应fd不再被通知,需要EPOLL_CTL_MOD后才能再次使用
          • 以上值可通过“位或"叠加后传递
      • 使用例子
        int epfd = epoll_create(50); //创建
        struct epoll_event event;
        event.events = EPOLLIN
        event.data.fd = sockfd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) //注册
        
      int epoll_wait(int epfd, struct epoll_event, int maxevents, int timeout)
      
      • epfd —— 上面创建的文件描述符
      • events —— 是一个epoll_event结构体指针
        • 代表连续的多个结构体,需要自行计算并分配空间,详情看例子
        • 函数会把发生事件的文件描述符存入这里面
      • maxevents —— 第二个参数可以保存的最大事件数
        • 可使用定义的常量EPOLL_SIZE
      • timeout —— 超时时间,以毫秒为单位
        • 若传递-1,则一直等待到有事件发生
      • 返回发生事件的文件描述符数量
      • 使用例子
        int event_cnt;
        struct epoll_event* ep_events;
        ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //查询