【Linux】IO多路复用——select,poll,epoll的概念和使用,三种模型的特点 ...

打印 上一主题 下一主题

主题 1605|帖子 1605|积分 4815

Linux多路复用

  IO多路复用是一种操作系统的技术,用于在单个线程或进程中管理多个输入输出操作。它的紧张目的是通过将多个IO操作合并到一个系统调用中来进步系统的性能和资源使用率,避免了传统的多线程或多进程模型中因为阻塞IO而导致的资源浪费和低效率题目。
  在IO多路复用中,通常使用的系统调用有 select()、poll()、epoll() 等,它们允许程序期待多个文件描述符(sockets、文件句柄等)中的任何一个变为可读或可写,然后再进行现实的IO操作。这种模型相比于传统的多线程或多进程模型,具有更高的并发处理能力和更低的系统开销。

  
1. select

1.1 select的概念

  系统提供select函数来实现多路复用输入/输出模型。
  select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  程序会停在select这里期待,直到被监视的文件描述符有一个或多个发生了状态改变。

  
1.2 select的函数使用

  1. int select(int nfds, fd_set *readfds, fd_set *writefds,
  2.                         fd_set *exceptfds, struct timeval *timeout);
复制代码
函数参数:
  nfds:是需要监视的最大的文件描述符值+1。
  readfds:需要检测的可读文件描述符的聚集。
  writefds:需要检测的可写文件描述符的聚集。
  exceptfds:需要检测的异常文件描述符的聚集。
  timeout:为布局体timeval,用来设置select()的期待时间;
  当timeout即是NULL:则体现select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了变乱;
  当timeout为0:仅检测描述符聚集的状态,然后立刻返回,并不期待外部变乱的发生。
  当timeout为特定的时间值:假如在指定的时间段里没有变乱发生,select将超时返回。
  此中的可读,可写,异常文件描述符的聚集是一个fd_set类型,fd_set是系统提供的位图类型,位图的位置是否是1,体现是否关系该变乱。
  比方:
    输入时:假如我们要关心 0 1 2 3 文件描述符
    0000 0000->0000 1111 比特位的位置,体现文件描述符的编号
         比特位的内容 0or1 体现是否需要内核关心
    输出时:
    0000 0100->此时体现文件描述符的编号
         比特位的内容 0or1哪些用户关心的fd 上面的读变乱已经就绪了,这里体现2描述符就绪了
  
  系统提供了关于fd_set的接口,便于我们使用位图:
  1. void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
  2. int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
  3. void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
  4. void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
复制代码
  
函数返回值:
  执行成功则返回文件描述词状态已改变的个数。
  假如返回0代表在描述词状态改变前已超过timeout时间,没有返回。
  当有错误发生时则返回-1,错误缘故原由存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。
  错误值可能为:
  EBADF 文件描述词为无效的或该文件已关闭
  EINTR 此调用被信号所中断
  EINVAL 参数n 为负值。
  ENOMEM 核心内存不足
  
select的执行过程:
  (1)执行fd_set set; FD_ZERO(&set);则set用位体现是0000,0000。
  (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) 。
  (3)若再到场fd=2,fd=1,则set变为0001,0011 。
  (4)执行select(6,&set,0,0,0)阻塞期待,体现最大文件描述符+1是6,监控可读变乱,立刻返回。
  (5)若fd=1,fd=2上都发生可读变乱,则select返回,此时set变为0000,0011。留意:没有变乱发生的fd=5被清空。
  
1.3 select的优缺点

select的特点:
  (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。一般巨细是1024,但是fd_set的巨细可以调整。
  (2)将fd到场select监控集的同时,还要再使用一个数据布局array生存放到select监控会合的fd。
    1. 是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
    2. 是select返回后会把以前到场的但并无变乱发生的fd清空,则每次开始select前都要重新从array取得fd逐一到场(FD_ZERO开始),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
  
select缺点
  (1)每次调用select, 都需要手动设置fd聚集, 从接口使用角度来说也非常不便。
  (2)每次调用select,都需要把fd聚集从用户态拷贝到内核态,这个开销在fd很多时会很大。
  (3)同时每次调用select都需要在内核遍历通报进来的全部fd,这个开销在fd很多时也很大。
  (4)select支持的文件描述符数量太小。
  
select使用代码:
  1. #pragma once
  2. #include <iostream>
  3. #include <sys/select.h>
  4. #include <sys/time.h>
  5. #include "Socket.hpp"
  6. using namespace std;
  7. static const uint16_t defaultport = 888;
  8. static const int fd_num_max = (sizeof(fd_set) * 8);
  9. int defaultfd = -1;
  10. class SelectServer
  11. {
  12. public:
  13.     SelectServer(uint16_t port = defaultport) : _port(port)
  14.     {
  15.         for (int i = 0; i < fd_num_max; i++)
  16.         {
  17.             fd_array[i] = defaultfd;
  18.             // std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
  19.         }
  20.     }
  21.     bool Init()
  22.     {
  23.         _listensock.Socket();
  24.         _listensock.Bind(_port);
  25.         _listensock.Listen();
  26.         return true;
  27.     }
  28.     void Accepter()
  29.     {
  30.         // 我们的连接事件就绪了
  31.         std::string clientip;
  32.         uint16_t clientport = 0;
  33.         int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会
  34.         if (sock < 0) return;
  35.         lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);
  36.         // sock -> fd_array[]
  37.         int pos = 1;
  38.         for (; pos < fd_num_max; pos++) // 第二个循环
  39.         {
  40.             if (fd_array[pos] != defaultfd)
  41.                 continue;
  42.             else
  43.                 break;
  44.         }
  45.         if (pos == fd_num_max)
  46.         {
  47.             lg(Warning, "server is full, close %d now!", sock);
  48.             close(sock);
  49.         }
  50.         else
  51.         {
  52.             fd_array[pos] = sock;
  53.             PrintFd();
  54.             // TODO
  55.         }
  56.     }
  57.     void Recver(int fd, int pos)
  58.     {
  59.         // demo
  60.         char buffer[1024];
  61.         ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
  62.         if (n > 0)
  63.         {
  64.             buffer[n] = 0;
  65.             cout << "get a messge: " << buffer << endl;
  66.         }
  67.         else if (n == 0)
  68.         {
  69.             lg(Info, "client quit, me too, close fd is : %d", fd);
  70.             close(fd);
  71.             fd_array[pos] = defaultfd; // 这里本质是从select中移除
  72.         }
  73.         else
  74.         {
  75.             lg(Warning, "recv error: fd is : %d", fd);
  76.             close(fd);
  77.             fd_array[pos] = defaultfd; // 这里本质是从select中移除
  78.         }
  79.     }
  80.     void Dispatcher(fd_set &rfds)
  81.     {
  82.         for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
  83.         {
  84.             int fd = fd_array[i];
  85.             if (fd == defaultfd)
  86.                 continue;
  87.             if (FD_ISSET(fd, &rfds))
  88.             {
  89.                 if (fd == _listensock.Fd())
  90.                 {
  91.                     Accepter(); // 连接管理器
  92.                 }
  93.                 else // non listenfd
  94.                 {
  95.                     Recver(fd, i);
  96.                 }
  97.             }
  98.         }
  99.     }
  100.     void Start()
  101.     {
  102.         int listensock = _listensock.Fd();
  103.         fd_array[0] = listensock;
  104.         for (;;)
  105.         {
  106.             fd_set rfds;
  107.             FD_ZERO(&rfds);
  108.             int maxfd = fd_array[0];
  109.             for (int i = 0; i < fd_num_max; i++) // 第一次循环
  110.             {
  111.                 if (fd_array[i] == defaultfd)
  112.                     continue;
  113.                 FD_SET(fd_array[i], &rfds);
  114.                 if (maxfd < fd_array[i])
  115.                 {
  116.                     maxfd = fd_array[i];
  117.                     lg(Info, "max fd update, max fd is: %d", maxfd);
  118.                 }
  119.             }
  120.             // accept?不能直接accept!检测并获取listensock上面的事件,新连接到来,等价于读事件就绪
  121.             // struct timeval timeout = {1, 0}; // 输入输出,可能要进行周期的重复设置
  122.             struct timeval timeout = {0, 0}; // 输入输出,可能要进行周期的重复设置
  123.             // 如果事件就绪,上层不处理,select会一直通知你!
  124.             // select告诉你就绪了,接下来的一次读取,我们读取fd的时候,不会被阻塞
  125.             // rfds: 输入输出型参数。 1111 1111 -> 0000 0000
  126.             int n = select(maxfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
  127.             switch (n)
  128.             {
  129.             case 0:
  130.                 cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
  131.                 break;
  132.             case -1:
  133.                 cerr << "select error" << endl;
  134.                 break;
  135.             default:
  136.                 // 有事件就绪了,TODO
  137.                 cout << "get a new link!!!!!" << endl;
  138.                 Dispatcher(rfds); // 就绪的事件和fd你怎么知道只有一个呢???
  139.                 break;
  140.             }
  141.         }
  142.     }
  143.     void PrintFd()
  144.     {
  145.         cout << "online fd list: ";
  146.         for (int i = 0; i < fd_num_max; i++)
  147.         {
  148.             if (fd_array[i] == defaultfd)
  149.                 continue;
  150.             cout << fd_array[i] << " ";
  151.         }
  152.         cout << endl;
  153.     }
  154.     ~SelectServer()
  155.     {
  156.         _listensock.Close();
  157.     }
  158. private:
  159.     Sock _listensock;
  160.     uint16_t _port;
  161.     int fd_array[fd_num_max];   // 数组, 用户维护的!
  162.     // int wfd_array[fd_num_max];
  163. };
复制代码
  
2. poll

2.1 poll的概念

  poll和select实现原理基本类似,
  poll只为了解决select的两个硬伤:
  1.期待的fd是有上限的,(底层类似链表储存实现,而不是位图)
  2.每次要对关心的fd进行变乱重置,(pollfd布局包罗了要监视的event和发生的event,使用前后不用初始化fd_set)
  
2.2 poll的函数使用

  1. int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  2. // pollfd结构
  3. struct pollfd {
  4.         int fd; /* file descriptor */
  5.         short events; /* requested events */
  6.         short revents; /* returned events */
  7. };
复制代码
函数参数表明:
  fds:是一个poll函数监听的布局列表. 每一个元素中, 包罗了三部门内容: 文件描述符, 监听的变乱聚集, 返回的变乱聚集。
  nfds:体现fds数组的长度。
  timeout:体现poll函数的超时时间, 单位是毫秒(ms)。

返回结果:
  返回值小于0, 体现堕落。
  返回值即是0, 体现poll函数期待超时。
  返回值大于0, 体现poll由于监听的文件描述符就绪而返回。
  
2.3 poll的优缺点

poll的优点
  (1)pollfd布局包罗了要监视的event和发生的event,不再使用select“参数-值”通报的方式. 接口使用比 select更方便。
  (2)poll并没有最大数量限制 (但是数量过大后性能也是会下降)。
poll的缺点
  (1)和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
  (2)每次调用poll都需要把大量的pollfd布局从用户态拷贝到内核中。
  (3)同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降。
  
poll使用代码:
  1. #pragma once
  2. #include <iostream>
  3. #include <poll.h>
  4. #include <sys/time.h>
  5. #include "../select/Socket.hpp"
  6. using namespace std;
  7. static const uint16_t defaultport = 8888;
  8. static const int fd_num_max = 64;
  9. int defaultfd = -1;
  10. int non_event = 0;
  11. class PollServer
  12. {
  13. public:
  14.     PollServer(uint16_t port = defaultport) : _port(port)
  15.     {
  16.         for (int i = 0; i < fd_num_max; i++)
  17.         {
  18.             _event_fds[i].fd = defaultfd;
  19.             _event_fds[i].events = non_event;
  20.             _event_fds[i].revents = non_event;
  21.             // std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
  22.         }
  23.     }
  24.     bool Init()
  25.     {
  26.         _listensock.Socket();
  27.         _listensock.Bind(_port);
  28.         _listensock.Listen();
  29.         return true;
  30.     }
  31.     void Accepter()
  32.     {
  33.         // 我们的连接事件就绪了
  34.         std::string clientip;
  35.         uint16_t clientport = 0;
  36.         int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会
  37.         if (sock < 0) return;
  38.         lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);
  39.         // sock -> fd_array[]
  40.         int pos = 1;
  41.         for (; pos < fd_num_max; pos++) // 第二个循环
  42.         {
  43.             if (_event_fds[pos].fd != defaultfd)
  44.                 continue;
  45.             else
  46.                 break;
  47.         }
  48.         if (pos == fd_num_max)
  49.         {
  50.             lg(Warning, "server is full, close %d now!", sock);
  51.             close(sock);
  52.             // 扩容
  53.         }
  54.         else
  55.         {
  56.             // fd_array[pos] = sock;
  57.             _event_fds[pos].fd = sock;
  58.             _event_fds[pos].events = POLLIN;
  59.             _event_fds[pos].revents = non_event;
  60.             PrintFd();
  61.             // TODO
  62.         }
  63.     }
  64.     void Recver(int fd, int pos)
  65.     {
  66.         // demo
  67.         char buffer[1024];
  68.         ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
  69.         if (n > 0)
  70.         {
  71.             buffer[n] = 0;
  72.             cout << "get a messge: " << buffer << endl;
  73.         }
  74.         else if (n == 0)
  75.         {
  76.             lg(Info, "client quit, me too, close fd is : %d", fd);
  77.             close(fd);
  78.             _event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
  79.         }
  80.         else
  81.         {
  82.             lg(Warning, "recv error: fd is : %d", fd);
  83.             close(fd);
  84.             _event_fds[pos].fd = defaultfd; // 这里本质是从select中移除
  85.         }
  86.     }
  87.     void Dispatcher()
  88.     {
  89.         for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
  90.         {
  91.             int fd = _event_fds[i].fd;
  92.             if (fd == defaultfd)
  93.                 continue;
  94.             if (_event_fds[i].revents & POLLIN)
  95.             {
  96.                 if (fd == _listensock.Fd())
  97.                 {
  98.                     Accepter(); // 连接管理器
  99.                 }
  100.                 else // non listenfd
  101.                 {
  102.                     Recver(fd, i);
  103.                 }
  104.             }
  105.         }
  106.     }
  107.     void Start()
  108.     {
  109.         _event_fds[0].fd = _listensock.Fd();
  110.         _event_fds[0].events = POLLIN;
  111.         int timeout = 3000; // 3s
  112.         for (;;)
  113.         {
  114.             int n = poll(_event_fds, fd_num_max, timeout);
  115.             switch (n)
  116.             {
  117.             case 0:
  118.                 cout << "time out... " << endl;
  119.                 break;
  120.             case -1:
  121.                 cerr << "poll error" << endl;
  122.                 break;
  123.             default:
  124.                 // 有事件就绪了,TODO
  125.                 cout << "get a new link!!!!!" << endl;
  126.                 Dispatcher(); // 就绪的事件和fd你怎么知道只有一个呢???
  127.                 break;
  128.             }
  129.         }
  130.     }
  131.     void PrintFd()
  132.     {
  133.         cout << "online fd list: ";
  134.         for (int i = 0; i < fd_num_max; i++)
  135.         {
  136.             if (_event_fds[i].fd == defaultfd)
  137.                 continue;
  138.             cout << _event_fds[i].fd << " ";
  139.         }
  140.         cout << endl;
  141.     }
  142.     ~PollServer()
  143.     {
  144.         _listensock.Close();
  145.     }
  146. private:
  147.     Sock _listensock;
  148.     uint16_t _port;
  149.     struct pollfd _event_fds[fd_num_max]; // 数组, 用户维护的!
  150.     // struct pollfd *_event_fds;
  151.     // int fd_array[fd_num_max];
  152.     // int wfd_array[fd_num_max];
  153. };
复制代码
  
3. epoll

3.1 epoll的概念



  epoll: 是为处理大批量句柄而作了改进的poll(真的是大改进)。
  epoll是IO多路复用技术,在实现上维护了一个用于返回触发变乱的Socket的链表和一个记录监听变乱的红黑树,epoll的高效体现在:
  (1)对监听变乱的修改是 log N(红黑树)。
  (2)用户程序无需遍历全部的Socket(发生变乱的Socket被放到链表中直接返回)。
  (3)内核无需遍历全部的套接字,内核使用回调函数在变乱发生时直接转到对应的处理函数。
  
3.2 epoll的函数使用

  epoll 有3个相干的系统调用:
epoll_create
  1. int epoll_create(int size);
复制代码
  创建一个epoll的句柄,自从linux2.6.8之后,size参数是被忽略的,用完之后, 必须调用close()关闭。
  
epoll_ctl
  1. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
复制代码
epoll的变乱注册函数:
  它不同于select()是在监听变乱时告诉内核要监听什么类型的变乱, 而是在这里先注册要监听的变乱类型。
  第一个参数是epoll_create()的返回值(epoll的句柄)。
  第二个参数体现动作,用三个宏来体现。
  第三个参数是需要监听的fd。
  第四个参数是告诉内核需要监听什么事。
第二个参数的取值:
  EPOLL_CTL_ADD :注册新的fd到epfd中。
  EPOLL_CTL_MOD :修改已经注册的fd的监听变乱。
  EPOLL_CTL_DEL :从epfd中删除一个fd。
struct epoll_event布局如下:

  
events可以是以下几个宏的聚集:
  EPOLLIN : 体现对应的文件描述符可以读 (包括对端SOCKET正常关闭)。
  EPOLLOUT : 体现对应的文件描述符可以写。
  EPOLLPRI : 体现对应的文件描述符有紧急的数据可读 (这里应该体现有带外数据到来)。
  EPOLLERR : 体现对应的文件描述符发生错误。
  EPOLLHUP : 体现对应的文件描述符被挂断。
  EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的。
  EPOLLONESHOT:只监听一次变乱, 当监听完这次变乱之后, 假如还需要继承监听这个socket的话, 需要再次把这个socket到场到EPOLL队列里。
  
epoll_wait
  1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
复制代码
网络在epoll监控的变乱中已经发送的变乱:
  参数events是分配好的epoll_event布局体数组。
  epoll将会把发生的变乱赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去资助我们在用户态中分配内存)。
  maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size。
  参数timeout是超时时间 (毫秒,0会立刻返回,-1是永久阻塞)。
  假如函数调用成功,返回对应I/O上已准备好的文件描述符数量,如返回0体现已超时, 返回小于0体现函数失败。
  
epoll原理:

  (1)当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll布局体,这个布局体中有两个成员与epoll的使用方式密切相干。
  (2)每一个epoll对象都有一个独立的eventpoll布局体,用于存放通过epoll_ctl方法向epoll对象中添加进来的变乱。
  (3)这些变乱都会挂载在红黑树中,如此,重复添加的变乱就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,此中n为树的高度)。
  (4)而全部添加到epoll中的变乱都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的变乱发生时会调用这个回调方法。
  (5)这个回调方法在内核中叫ep_poll_callback,它会将发生的变乱添加到rdlist双链表中。
  (6)在epoll中,对于每一个变乱,都会建立一个epitem布局体。
  (7)当调用epoll_wait检查是否有变乱发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。
  (8)假如rdlist不为空,则把发生的变乱复制到用户态,同时将变乱数量返回给用户. 这个操作的时间复杂度是O(1)。
  
  1. struct eventpoll{
  2.         ....
  3.         /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
  4.         struct rb_root rbr;
  5.         /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
  6.         struct list_head rdlist;
  7.         ....
  8. };
复制代码
  1. struct epitem{
  2.         struct rb_node rbn;//红黑树节点
  3.         struct list_head rdllink;//双向链表节点
  4.         struct epoll_filefd ffd; //事件句柄信息
  5.         struct eventpoll *ep; //指向其所属的eventpoll对象
  6.         struct epoll_event event; //期待发生的事件类型
  7. }
复制代码
总结一下, epoll的使用过程简单看就三步:
  (1)调用epoll_create创建一个epoll句柄。
  (2)调用epoll_ctl, 将要监控的文件描述符进行注册。
  (3)调用epoll_wait, 期待文件描述符就绪。
  
3.3 epoll的优点

  (1)接口使用方便: 虽然拆分成了三个函数,但是反而使用起来更方便高效,不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开。
  (2)数据拷贝轻量: 只在符合的时候调用 EPOLL_CTL_ADD 将文件描述符布局拷贝到内核中,这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)。
  (3)变乱回调机制: 避免使用遍历,而是使用回调函数的方式,迁就绪的文件描述符布局到场到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪,这个操作时间复杂度O(1),即使文件描述符数量很多,效率也不会受到影响。
  (4)没有数量限制: 文件描述符数量无上限。
  
3.4 epoll工作模式

  epoll默认:LT模式,变乱到来但是上层不处理,高电平,一直有效。
          ET模式,数据大概连接,从无到有,从有到多,变化的时候才通知我们一次。
  ET的通知效率更高:倒逼程序员,每次通知都必须把本轮的数据取走 -> 循环读取,读取错误 -> fd默认是阻塞的 -> ET,全部的fd必须黑白阻塞的。
  ET的IO效率也更高 -> tcp会向对方通告一个更大的窗口,从而概率上让对方一次给我发生更多数据,假如LT每次也可以就绪,那效率差不多。
  本质就是向就绪队列,添加一次大概是多次就绪节点。
  
Epoller.hpp Epoller对epoll进行封装
  1. #pragma once
  2. #include "nocopy.hpp"
  3. #include <sys/epoll.h>
  4. #include "Log.hpp"
  5. #include <cstring>
  6. #include <cerrno>
  7. //封装我们的epoll,epoll公有继承于我们的nocopy类,不能被拷贝
  8. class Epoller: public nocopy
  9. {
  10.     static const int size=128;
  11. public:
  12.     Epoller()
  13.     {
  14.         _epfd=epoll_create(size);
  15.         if(_epfd<0)
  16.         {
  17.             lg(Error,"epoll_create error: %s",strerror(errno));
  18.         }
  19.         else
  20.         {
  21.             lg(Info,"epoll_create success: %d",_epfd);
  22.         }
  23.     }
  24.     //进行epoll事件等待
  25.     //返回的是就绪事件的数量
  26.     int EpollerWait(struct epoll_event revents[], int num)
  27.     {
  28.         int n=epoll_wait(_epfd,revents,num,-1/*_timeout*/);
  29.         return n;
  30.     }
  31.     //我们所要更新的时间操作和套接字监控的事件
  32.     int EpollerUpdate(int oper, int sock, uint32_t event)
  33.     {
  34.         int n=0;
  35.         if(oper==EPOLL_CTL_DEL) //删除操作
  36.         {
  37.             n=epoll_ctl(_epfd,oper,sock,nullptr);
  38.             if(n!=0)
  39.             {
  40.                 lg(Error,"epoll_ctl delete error!");
  41.             }
  42.         }
  43.         else //新增和修改
  44.         {
  45.             struct epoll_event ev;
  46.             ev.events=event;
  47.             ev.data.fd=sock; //传入sock,方便我们知道是哪一个fd就绪
  48.             //完成了我们对于哪一个文件和那一个文件的描述符进行事件关心
  49.             //接下来进行注册
  50.             n=epoll_ctl(_epfd,oper,sock,&ev);
  51.             if(n!=0)
  52.             {
  53.                 lg(Error,"epoll_ctl error!");
  54.             }
  55.         }
  56.         return n;
  57.     }
  58.     ~Epoller()
  59.     {
  60.         if(_epfd>0)
  61.         {
  62.             close(_epfd);
  63.         }
  64.     }
  65. private:
  66.     int _epfd;
  67.     int _timeout{3000};
  68. };
复制代码
  
EpollServer.hpp Epoll服务器
  1. #pragma once
  2. #include <iostream>
  3. #include <memory>
  4. #include <sys/epoll.h>
  5. #include "Socket.hpp"
  6. #include "Epoller.hpp"
  7. #include "Log.hpp"
  8. #include "nocopy.hpp"
  9. uint32_t EVENT_IN = (EPOLLIN); //表示更新读事件
  10. uint32_t EVENT_OUT = (EPOLLOUT); //表示更新写事件
  11. class EpollServer : public nocopy
  12. {
  13.     static const int num = 64;
  14. public:
  15.     EpollServer(uint16_t port)
  16.         : _port(port),
  17.           _listsocket_ptr(new Sock()),
  18.           _epoll_ptr(new Epoller())
  19.     {}
  20.     void Init()
  21.     {
  22.         _listsocket_ptr->Socket();
  23.         _listsocket_ptr->Bind(_port);
  24.         _listsocket_ptr->Listen();
  25.         lg(Info,"create listen socket success: %d\n",_listsocket_ptr->Fd());
  26.     }
  27.     void Accepter()
  28.     {
  29.         //获取了一个连接   
  30.         std::string clientip;
  31.         uint16_t clientport;
  32.         int sock=_listsocket_ptr->Accept(&clientip,&clientport);
  33.         if(sock>0)
  34.         {
  35.             //我们不能直接读取数据
  36.             //ssize_t n=read(sock,...);
  37.             _epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,EVENT_IN);
  38.             lg(Info,"get a new link, client info @ %s:%d",clientip.c_str(),clientport);
  39.         }
  40.     }
  41.     void Recver(int fd)
  42.     {
  43.         // demo
  44.         char buffer[1024];
  45.         ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
  46.         if (n > 0)
  47.         {
  48.             buffer[n] = 0;
  49.             std::cout << "get a messge: " << buffer << std::endl;
  50.             // wrirte
  51.             std::string echo_str = "server echo $ ";
  52.             echo_str += buffer;
  53.             write(fd, echo_str.c_str(), echo_str.size());
  54.         }
  55.         else if (n == 0)
  56.         {
  57.             lg(Info, "client quit, me too, close fd is : %d", fd);
  58.             //细节3
  59.             _epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
  60.             close(fd);
  61.         }
  62.         else
  63.         {
  64.             lg(Warning, "recv error: fd is : %d", fd);
  65.             _epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
  66.             close(fd);
  67.         }
  68.     }
  69.     void Dispatcher(struct epoll_event revs[], int num)
  70.     {
  71.         //遍历获取文件描述符中已经就绪的事件
  72.         for(int i=0;i<num;i++)
  73.         {
  74.             uint32_t events=revs[i].events;
  75.             int fd=revs[i].data.fd;
  76.             if(events & EVENT_IN) //判断事件类型,这是读事件就绪
  77.             {
  78.                 if(fd==_listsocket_ptr->Fd())
  79.                 {
  80.                     //获取了一个连接   
  81.                     Accepter();
  82.                 }
  83.                 else
  84.                 {
  85.                     //其他fd上面的普通读取事件就绪
  86.                     Recver(fd);
  87.                 }
  88.             }
  89.             else if(events & EVENT_OUT) //写事件就绪
  90.             {
  91.             }
  92.             else
  93.             {
  94.             }
  95.         }
  96.     }
  97.     //开始我们的epoll事件监听
  98.     void Start()
  99.     {
  100.         //将listensock添加到epoll中 -> listensock和他关心的事件,添加到内核epoll模型的rb_tree
  101.         _epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,_listsocket_ptr->Fd(),EVENT_IN);
  102.         //我们将我们的监听套接字listsocket给epoll进行读事件管理,接下来由红黑树自动关心我们的事件
  103.         struct epoll_event revs[num];
  104.         for(;;)
  105.         {
  106.             int n=_epoll_ptr->EpollerWait(revs,num);
  107.             if(n>0)
  108.             {
  109.                 //有事件就绪
  110.                 lg(Debug,"event happend, fd is %d",revs[0].data.fd);
  111.                 //处理就绪事件
  112.                 Dispatcher(revs,n);
  113.             }
  114.             else if(n==0)
  115.             {
  116.                 lg(Info,"time out...");
  117.             }
  118.             else
  119.             {
  120.                 lg(Error,"epoll wait error");
  121.             }
  122.         }
  123.     }
  124.     ~EpollServer()
  125.     {
  126.         _listsocket_ptr->Close();
  127.     }
  128. private:
  129.     std::shared_ptr<Sock> _listsocket_ptr;
  130.     std::shared_ptr<Epoller> _epoll_ptr;
  131.     uint16_t _port;
  132. };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表