目录
一、功能
针对大量描述符进行IO事件监控,让进程可以只针对就绪的描述符进行IO操作,提高IO效率,避免针对未就绪描述符操作而导致的效率低或阻塞。
引入:
二、应用场景
多路转接模型:是监控大量描述符,然后针对就绪描述符逐个进行处理。
三、多路转接模型的实现
1.select模型
1.1操作流程
1.2相关接口
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:几种事件的描述符集合中,最大的描述符的值+1;
readfds:可读事件的描述符集合;
writefds:可写事件的描述符集合;
exceptfds:异常事件的描述符集合;
timeout:struct timeval{ uint32_t tv_usec; uint32_t tv_sec};监控超时等待时间;
返回值:
成功,返回就绪的事件个数;出错,返回-1;没有描述符就绪,返回0。
辅助接口:
1.3示例
对标准输入进行可读事件的监控,在描述符可读时从标准输入读取数据。
#include<stdio.h>
#include<unistd.h>
#include<sys/select.h>
#include<time.h>
//#include<sys/socket.h>
int main() {
  fd_set rfds;
  //1.初始化集合
  FD_ZERO(&rfds);
  while (1) {
    struct timeval tv;
    tv.tv_usec = 0;
    tv.tv_sec = 3;//设置超时时间为3s
    //2.将标准输入描述符添加到集合中
    FD_SET(0, &rfds);//0是标准输入描述符
    int max_fd = 0;
    //因为select会修改rfds集合(返回前会将未就绪的描述符剔除),所以需要在循环内每次添加
    int ret = select(max_fd + 1, &rfds, NULL, NULL, &tv);
    if (ret < 0) {
      perror("select error!\n");
      usleep(1000);
      continue;
    }
    else if (ret == 0) {//没有描述符就绪
      printf("wait timeout!\n");
      usleep(1000);
      continue;
    }
    //有描述符就绪
    for (int i = 0; i <= max_fd; ++i) {
      if (FD_ISSET(i, &rfds)) {//若i在集合中,说明描述符i就绪
        char buf[1024] = {0};
        int res = read(i, buf, 1023);
        if (res < 0) {//接收数据出错
          perror("recv error!\n");
          FD_CLR(i, &rfds);
          return -1;
        }
        printf("描述符%d就绪, 读取数据为: %s\n", i, buf);
      }
    }
  }
  return 0;
}实现效果:

1.4常见使用方式
封装一个Select类,实例化的每个Select对象都是一个多路转接对象,能够完成对大量描述符的监控,向外界返回就绪的描述符数组。
class Select{
    private:
        fd_set _rfds;//可读事件的描述符集合的备份,每次监控都是从该集合复制一份出来进行监控
        int _max_fd;
    public:
        Select(){//针对成员变量进行初始化}
        bool Add(const TCPsocket& sock);//将sock中的描述符fd,添加到rfds可读事件描述符集合中
        
        bool Del(const TCPsocket& sock);//将sock中的描述符fd,从rfds可读事件描述符集合中移除
        bool Wait(std::vector<TCPsocket>& array);//开始监控,返回就绪的描述符的数组
};1.5优缺点
优点:
缺点:
2.poll模型
poll模型:也是针对大量描述符进行监控,但是poll的监控是为每个描述符设置了一个事件结构体。
2.1操作流程
2.2相关接口
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:事件结构体的首地址;
nfds:数组中有效元素的个数;
timeout:监控超时时间,以毫秒为单位;
返回值:
返回实际就绪的事件个数;返回0,表示监控超时;返回值小于零,表示出错。
事件结构体pollfd:
struct pollfd{
    int fd;//要监控的事件描述符
    short events;//针对这个描述符要监控的事件
    //POLLIN-可读、POLLOUT-可写
    short revents;//监控调用返回后,这个描述符实际就绪的事件
};2.3示例
利用poll模型,监控标准输入的可读事件:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<poll.h>
int main() {
  struct pollfd pfds[10];
  int poll_count = 0;
  pfds[0].fd = 0;//设置要监控的描述符是标准输入
  pfds[0].events = POLLIN;//针对标准输入要监控的是可读事件
  poll_count++;
  while (1) {
    int ret = poll(pfds, 1, 3000);
    if (ret < 0) {
      perror("poll error!");
      usleep(1000);
      continue;
    }
    else if (ret == 0) {
      printf("poll timeout!\n");
      usleep(1000);
      continue;
    }
    for (int i = 0; i < poll_count; ++i) {
      if (pfds[i].revents & POLLIN) {//就绪了可读事件
        char buf[1024] = {0};
        read(pfds[i].fd, buf, 1023);
        printf("描述符%d就绪,读取数据为:%s\n", pfds[i].fd, buf);
      }
      else if (pfds[i].revents & POLLOUT) {
        //就绪的是可写事件,对应操作
      }
    }
  }
  return 0;
}实现效果:

2.4优缺点
优点:
缺点:
3.epoll模型
epoll模型:针对大量描述符进行事件监控(被认为是Linux2.6以后最好用的多路转接模型)。
3.1操作流程
(1)创建epoll句柄
在内核中会创建struct eventpoll,含有两个重要成员:rbr(红黑树)和rdllist(双向链表)。
(2)向epoll添加监控
组织对应事件结构体,添加到epoll红黑树中。
(3)开始监控
注意:
3.2相关接口
(1)创建句柄
(2)添加监控
(3)开始监控
事件结构体epoll_event:
struct epoll_event{
    uint32_t events;//要监控的事件,以及监控返回后实际就绪的事件
    //EPOLLIN-可读、EPOLLOUT-可写
    epoll_data_t data;//用户数据变量
};
typedef union epoll_data{
    void *ptr;
    int fd;
}epoll_data_t;3.3常见使用方式
封装Epoll类,实例化的对象可以实现对大量描述符的监控,并返回就绪的描述符。
class Epoll{
    private:
        int _epfd;///epoll操作句柄
    public:
        Epoll(){}
        bool Add(TCPsocket& sock);//对sock描述符添加epoll监控
        bool Del(TCPsocket& sock);//移除对sock描述符的监控
        bool Wait(std::vector<TCPsocket> *array, int timeout = 3000);//开始监控
};示例:
#include "socket_tcp.hpp"
#include <vector>
#include <sys/epoll.h>
#include <cstdlib>
#define EPOLL_MAX 10
class Epoll{
  private:
    int _epfd;///epoll操作句柄
  public:
    Epoll():_epfd(-1) {
      _epfd = epoll_create(1);
      if (_epfd < 0) {
        perror("epoll error!");
        exit(0);
      }
    }
    bool Add(TCPsocket& sock) {//对sock描述符添加epoll监控
      struct epoll_event ev;
      ev.events = EPOLLIN;//对可读事件进行监控
      ev.data.fd = sock.GetFd();
      int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, sock.GetFd(), &ev);
      if (ret < 0) {
        perror("epoll_ctl error!");
        return false;
      }
      return true;
    }
    bool Del(TCPsocket& sock){//移除对sock描述符的监控
      int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, sock.GetFd(), NULL);
      if (ret < 0) {
        perror("epoll_ctl error!");
        return false;
      }
      return true;
    }
    bool Wait(std::vector<TCPsocket> *array, int timeout = 3000){//开始监控
      struct epoll_event evs[EPOLL_MAX];
      int ret = epoll_wait(_epfd, evs, EPOLL_MAX, timeout);
      if (ret < 0) {
        perror("epoll_wait error!");
        return false;
      }
      else if (ret == 0) {
        printf("epoll timeout!\n");
        return false;
      }
      //返回ret个就绪的描述符对应的事件结构体,保存在evs中
      for (int i = 0; i < ret; ++i) {
        if (evs[i].events & EPOLLIN) {//只需要可读事件就绪的描述符
          TCPsocket sock;
          sock.SetFd(evs[i].data.fd);
          array -> push_back(sock);
        }
      }
      return true;
    }
};实现效果:

3.4epoll的事件触发方式
(一)水平触发(默认的触发方式)
触发特性:
(二)边缘触发(EPOLLET)
触发特性:
属性相关接口:
int fcntl(int fd, int cmd, int arg……);
边缘触发适用场景:
3.5优缺点
缺点:
优点
select&poll相较于epoll的优点:
注:自了解epoll惊群问题。










