epoll模型

poll模型

         poll采用一个描述符事件结构的方式对描述符所关心的事件进行监控;

                   1.所关心的描述符

                   2.描述符所关心的事件   POLLIN (可读) |  POLLOUT(可写)

                   3.描述符实际监控时就绪的事件     

  1.  定义事件结构数组,将用户所关心的描述符和事件添加到结构数组中
  2. 将事件结构拷贝到内核当中;进行轮询遍历方式的监控;若有描述符就绪,则返回          
  3. 若有描述符就绪,则修改这个描述符事件结构中的实际就绪事件

  

  1. fds是一个polll函数监听的结构列表,每一个元素,包含了三部分元素:文件描述符,监听的事件集合,返回的事件集合
  2. nfds表示fds数组的长度
  3. timeout表示poll函数的超时时间,单位毫秒

poll优缺点分析

     优点:

             1.描述符没有具体上限

             2.简化了操作流程(采用事件结构监控方式简化了select三种事件集合监控流程)

     缺点:

             1.poll采用轮询遍历方式判断就绪,性能随着描述符增多而下降

             2.poll不会告诉用户具体的就绪描述符;需要用户进行轮询判断;性能随着增多而下降

             3.代码复杂度较高

             4.不能跨平台

epoll模型(Linux下能最高的io多路转接模型)

                事件就绪:

                               可读事件就绪:接收缓冲区的数据大小大于低水位标记(1B),就会触发可读事件

                               可写事件就绪:发送缓冲区的空闲空间大小大于低水位标记(1B),就会触发可写事件

                  接口:

                               int   epoll_create(int size) ; //创建epoll

                                       功能:在内核中创建一个结构体:struct eventpoll{rbr--红黑树,  rdlist - 双向链表}

                                       size:能监控的描述符上限,linux2.6.8内核之后,被忽略--只要>0就可以

                                       返回值:文件描述符--非负整数--epoll的操作句柄

                              int   epoll_ctl(int epfd , int op , int fd, struct epoll_event* event);

                                      功能:向内核中epfd所对应的epoll的结构进行添加/删除/修改/一个fd所关心的事件event,epoll也是采用事件结构的                                                   形式对描述符进行监控

                                      epfd:   epoll_create返回的操作句柄

                                      op:      用户要进行的操作

                                                 EPOLL_CTL_DEL   从内核的eventpoll中移除要监控的事件结构

                                                 EPOLL_CTL_MOD 修改内核中所监控的事件结构

                                      fd:       用户所要监控的描述符

                                      event: 描述符对应所要监控的事件

                                                  struct  poll_event{

                                                            uint32_t  events;     用户对描述所关心的事件   EPOLLIN|EPOLLOUT|EPOLLET|EPOLLLT

                                                            epoll_data_t data;   事件对应的数据(描述符就绪后就会返回事件结构,用户可以获得这个数据,

                                                                                            若这个数据就是所关心的描述符;用户就可以直接只用这个描述符进行操作了)

                                                  };

                                                  typedef   union  epoll_data{

                                                              void*  ptr; 

                                                              int      fd;

                                                 }epoll_data_t;

                                   int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout);

                                         功能:epoll操作句柄

                                         events:事件结构数组-用于保存就绪描述符对应的事件

                                         maxevents: 用于确定一次最多获取的就绪事件个数(防止events溢出)

                                         timeout :  超时等待时间设置--毫秒

                                         返回值:<0出错                  ==0超时等待          >0 就绪的事件个数

                  eventpoll{

                       rbr--红黑树--保存用户添加的事件结构节点

                       rdlist--双向链表

                };

                

 

epoll触发方式 

  1. 告诉内核要开始对描述进行监控
  2. 操作系统对描述符进行事件监控---采用的是事件触发方式,为每一个要监控的描述符都定义了一个事件,并且对这一个事件定义一个事件回调函数
  3. 这个事件回调函数要做的事情就是,将就绪的描述符所对应的epoll_event事件添加到双向链表当中
  4. epoll_wait并不是立即返回,而是没隔一会就会看一下,内核当中eventpoll中的双向链表是否为空;进而判断是否有描述符就绪
  5. 若链表不为空,表示有描述符就绪,则epoll_wait即将返回;再返回之前,就将就绪的描述符对应事件结构向用户态的结构数组拷贝一份
  6. epoll会将就绪的描述符以及对应的事件,拷贝一份到用户态,直接告诉用户有哪些描述符就绪,进而用户就可以直接操作就绪的描述符

         水平触发(EPOLLLT):只要缓冲区的数据大小/空闲空间大于低水位标记,就会触发可读/可写就绪事件

         边沿触发(EPOLLET):

                        可读事件:每次只有新数据到来时才会出发一次可读事件(不关注缓冲区数据,要求用户一次将缓冲区数据全部读取)

                        可写事件:每次只有缓冲区空闲空间从0变为大于低水位标记的时候才会触发可写事件

                        读写混合:在混合监控的时候,对于可写事件就会触发边沿触发(防止可写事件每次不写入数据,但是有空闲空间都会触                                              发事件,(但是有没有数据可写))

                                           若是可读事件被设置为边沿触发,需要用户一次性将所有数据读取完毕;但是因为不知道数据有多少因此只能                                             从缓冲区中读取数据,当循环读取数据但是缓冲区中没有数据的时候,recv就会阻塞;

                       因此边沿触发的可读事件的描述符通常需要被设置为非阻塞

 代码实现(epoll)

    8 #include <iostream>
    9 #include <vector>
   10 #include <sys/epoll.h>
   11 #include "tcpsocket.hpp"
   12 
   13 class Epoll {
   14     private:
   15         int _epfd;
   16     public:
   17         bool Init() {
   18             //创建epoll
   19             //int epoll_create(int size);
   20             _epfd = epoll_create(1);
   21             if (_epfd < 0) {
   22                 perror("epoll create error");
   23                 return false;
   24             }                                                                    
   25             return true;
   26         }
   27         bool Add(TcpSocket sock, uint32_t events = 0) {
   28             sock.SetNonBlock();
   29             int fd = sock.GetSockFd();
   30             //epoll_ctl(int epfd,int op,int fd,epoll_event *event);
   31             struct epoll_event ev;
   32             ev.events = EPOLLIN | events;
   33             ev.data.fd = fd;
   34             int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
   35             if (ret < 0) {
   36                 perror("epoll ctrl error");
   37                 return false;
   38             }
   39             return true;
   40         }
   41         bool Del(TcpSocket sock) {
   42             int fd = sock.GetSockFd();
   43             int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, NULL);
   44             if (ret < 0) {
   45                 perror("epoll ctrl error");
   46                 return false;
   47             }
   48             return true;
   49         }                                                                        
   50         bool Wait(std::vector<TcpSocket> &list, int ms_timeout = 3000) {
   51             //int epoll_wait(int epfd, struct epoll_event *events,
   52             //  int maxevents, int timeout);
   53             struct epoll_event evs[10];
   54             int nfds = epoll_wait(_epfd, evs, 10, ms_timeout);
   55             if (nfds < 0) {
   56                 perror("epoll wait error");
   57                 return false;
   58             }else if (nfds == 0) {
   59                 std::cout << "epoll wait timeout\n";
   60                 return false;
   61             }
   62             for (int i = 0; i < nfds; i++) {
   63                 int fd = evs[i].data.fd;
   64                 TcpSocket sock;
   65                 sock.SetSockFd(fd);
   66                 list.push_back(sock);
   67             }                                                                    
   68             return true;
   69         }
   70 };
   71 
   72 int main()
   73 {
   74     TcpSocket lst_sock;
   75     CHECK_RET(lst_sock.Socket());
   76     CHECK_RET(lst_sock.Bind("0.0.0.0", 9000));
   77     CHECK_RET(lst_sock.Listen());
   78 
   79     Epoll epoll;
   80     CHECK_RET(epoll.Init());
   81     CHECK_RET(epoll.Add(lst_sock, EPOLLET));
   82     while(1) {
   83         std::vector<TcpSocket> list;
   84         bool ret = epoll.Wait(list);
   85         if (ret == false) {                                                      
   86             continue;
   87         }
W> 88         for (int i = 0; i < list.size(); i++) {
   89             if (list[i].GetSockFd() == lst_sock.GetSockFd()) {
   90                 TcpSocket cli_sock;
   91                 lst_sock.Accept(cli_sock);
   92                 epoll.Add(cli_sock, EPOLLET);
   93             }else {
   94                 std::string buf;
   95                 list[i].Recv(buf);
   96                 std::cout << "client say: " << buf << std::endl;
   97             }
   98         }
   99 
  100     }
  101     lst_sock.Close();
  102     return 0;
  103 }         

TcpSocket

   25 #include <string>
   26 #include <stdio.h>
   27 #include <unistd.h>
   28 #include <stdlib.h>
   29 #include <errno.h>
   30 #include <string.h>
   31 #include <fcntl.h>
   32 #include <netinet/in.h>
   33 #include <arpa/inet.h>
   34 #include <sys/socket.h>                                                          
   35 
   36 #define CHECK_RET(q) if((q) == false){return -1;}
   37 
   38 class TcpSocket
   39 {
   40     public:
   41         TcpSocket() : _sockfd(-1){
   42         }
   43         void SetSockFd(int fd){
   44             _sockfd = fd;
   45         }                                                                        
   46         int GetSockFd() {
   47             return _sockfd;
   48         }
   49         bool Socket() {
   50             _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   51             if (_sockfd < 0) {
   52                 perror("socket error");
   53                 return false;
   54             }
   55             return true;
   56         }
   57         bool SetNonBlock() {
   58             //int fcntl(int fd, int cmd, ... /* arg */ );
   59             //设置/获取描述符状态属性信息
   60             //  cmd:    
   61             //      F_GETFL 获取
   62             //      F_SETFL 设置
   63             int flag = fcntl(_sockfd, F_GETFL, 0);
   64             fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
   65             return true;
   66         }
   67         bool Bind(const std::string &ip, const uint16_t port){
   68             struct sockaddr_in addr;
   69             addr.sin_family = AF_INET;
   70             addr.sin_port = htons(port);                                         
   71             addr.sin_addr.s_addr = inet_addr(ip.c_str());
   72 
   73             socklen_t len = sizeof(struct sockaddr_in);
   74             int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
   75             if (ret < 0) {
   76                 perror("bind error");
   77                 return false;
   78             }
   79             return true;
   80         }
   81         bool Listen(const int backlog = 10) {                                    
   82             //int listen(int sockfd, int backlog);
   83             //backlog:最大并发连接数--内核中已完成连接队列的最大节点数
   84             int ret = listen(_sockfd, backlog);
   85             if (ret < 0) {
   86                 perror("listen error");
   87                 return false;
   88             }
   89             return  true;
   90         }
   91         bool Connect(const std::string &ip, const uint16_t port) {
   92             //int connect(int fd, struct sockaddr *addr,socklen_t len);
   93             //addr: 要连接的服务端地址信息
   94             struct sockaddr_in addr;
   95             addr.sin_family = AF_INET;
   96             addr.sin_port = htons(port);
   97             addr.sin_addr.s_addr = inet_addr(ip.c_str());
   98             socklen_t len = sizeof(struct sockaddr_in);
   99 
  100             int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
  101             if (ret < 0) {
  102                 perror("connect error");
  103                 return false;
  104             }
  105             return true;
  106         }                                                                        
  107         bool Accept(TcpSocket &csock, struct sockaddr_in *addr = NULL){
  108             //int accept(int sockfd, sockaddr *addr, socklen_t *len);
  109             //addr: 客户端的地址信息
  110             //len: 输入输出参数,既要指定接收长度,还要接收实际长度
  111             //返回值:为新客户端新建的socket套接字描述符
  112             //通过这个返回的描述符可以与指定的客户端进行通信
  113             struct sockaddr_in _addr;
  114             socklen_t len = sizeof(struct sockaddr_in);
  115             int newfd = accept(_sockfd, (struct sockaddr*)&_addr, &len);
  116             if (newfd < 0) {
  117                 perror("accept error");
  118                 return false;
  119             }
  120             if (addr != NULL) {
  121                 memcpy(addr, &_addr, len);
  122             }
  123             csock.SetSockFd(newfd);
  124             //_sockfd--仅用于接收新客户端连接请求                                
  125             //newfd----专门用于与客户端进行通信
  126             return true;
  127         }
  128         bool Recv(std::string &buf) {
  129             char tmp[4096] = {0};
  130             //ssize_t recv(int sockfd, void *buf, size_t len, int flags)
  131             //flags:0-默认阻塞接收  MSG_PEEK-获取数据但是不从缓冲区移除
  132             //返回值:实际接收的数据长度    失败:-1    连接断开:0
  133             while(1) {
  134                 int ret = recv(_sockfd, tmp, 5, 0);
  135                 if (ret < 0) {
  136                     //EAGAIN/EWOULDBLOCK 表示缓冲区没有数据
  137                     //EINTR 表示当前读取操作被信号打断
  138                     if (errno == EAGAIN || errno == EWOULDBLOCK 
  139                             || errno == EINTR) {
  140                         return true;
  141                     }
  142                     perror("recv error");                                        
  143                     return false;
  144                 }else if (ret == 0) {
  145                     printf("peer shutdown\n");
  146                     return false;
  147                 }
  148                 tmp[ret] = '\0';
  149                 buf += tmp;
  150             }
  151             return true;
  152         }
  153         bool Send(const std::string &buf) {
  154             //ssize_t send(int sockfd, void *buf, size_t len, int flags)
  155             int ret = send(_sockfd, buf.c_str(), buf.size(), 0);
  156             if (ret < 0) {
  157                 perror("send error");
  158                 return false;
  159             }
  160             return true;                                                         
  161         }
  162         bool Close() {
  163             close(_sockfd);
  164             _sockfd = -1;
W>165         }
  166     private:
  167         int _sockfd;
  168 };    

epoll优缺点分析

优点:

  1. epoll没有监控的描述符上限
  2. 采用了事件结构的方式简化了select这种监控集合的监控流程
  3. epoll采用一个异步阻塞操作;发起调用,让操作系统进行描述符监控
  4. 操作系统使用事件回调的方式对描述符进行监控--避免了select的轮询遍历,因此性能不会随着描述符的增多而降低
  5. epoll发起调用之后进行等待--循环判断内核当中epoll就绪的事件链表是否为空来确定是否有事件就绪,则将对应的事件拷贝到用户态让用户操作
  6. 直接告诉了用户那些描述符就绪了,可以直接对描述符进行操作;没有空遍历;提高性能,并且简化了代码流程
  7. epoll描述符事件结构只需要向内核拷贝一次,(内核当中的红黑树当中);不需要每次拷贝

缺点:

不能跨平台,只能精确到毫秒

适用场景:有大量的客户端连接,但是同一时间只有少量活跃的场景

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值