Socket编程实践(11) --epoll原理与封装

常用模型的特点

    Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 select/polL模型和epoll模型。

 

1 、PPC/TPC 模型

    这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我.只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程/线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右

2 、select 模型

    1) 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …

    2) 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!

    3) 内核/用户空间内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

3、 poll 模型

    基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。

 

Epoll 的提升

    1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max[599534] 察看。

    2. 效率提升, Epoll最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll的效率就会远远高于 select 和 poll 。

    3. 内存拷贝, Epoll 在这点上使用了“共享内存”,这个内存拷贝也省略了。


epoll的使用

epoll的接口非常简单,一共就3/4个函数:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int epoll_create(int size);  
  2. int epoll_create1(int flags);  
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  4. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);  

   1. 对于epoll_create1 的flag参数: 可以设置为0 或EPOLL_CLOEXEC,为0时函数表现与epoll_create一致, EPOLL_CLOEXEC标志与open 时的O_CLOEXEC 标志类似,即进程被替换时会关闭打开的文件描述符(需要注意的是,epoll_create与epoll_create1当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/<pid>/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽)。

   2. 对于epoll_ctl, op参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD

注册新的fd到epfd中

EPOLL_CTL_DEL

从epfd中删除一个fd

EPOLL_CTL_MOD

修改已经注册的fd的监听事件

   3. 对于epoll_wait:

      events:结构体指针, 一般是一个数组

      maxevents:事件的最大个数, 或者说是数组的大小

      timeout:超时时间, 含义与poll的timeout参数相同,设为-1表示永不超时;

   4. epoll_event结构体

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct epoll_event  
  2. {  
  3.     uint32_t     events;      /* Epoll events */  
  4.     epoll_data_t data;        /* User data variable */  
  5. };  
  6. typedef union epoll_data  
  7. {  
  8.     void        *ptr;  
  9.     int          fd;  
  10.     uint32_t     u32;  
  11.     uint64_t     u64;  
  12. } epoll_data_t;  

一般data 共同体我们设置其成员fd即可,也就是epoll_ctl 函数的第三个参数。

events集合

EPOLLIN

表示对应的文件描述符可以读(包括对端SOCKET正常关闭)

EPOLLOUT

表示对应的文件描述符可以写

EPOLLPRI

表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

EPOLLERR

表示对应的文件描述符发生错误

EPOLLHUP

表示对应的文件描述符被挂断

EPOLLET

将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

EPOLLONESHOT

只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /**示例: epoll使用示例 
  2.     注:client端与测试端与前同, 而且使用相同的测试端测试select/poll/epoll, 可以发现epoll的效率是非常高的**/  
  3.   
  4. //添加fd到epoll  
  5. void addFd(int epollfd, int fd, const uint32_t &events = EPOLLIN, bool et = false)  
  6. {  
  7.     struct epoll_event event;  
  8.     event.events = events;  
  9.     if (et)  
  10.         event.events |= EPOLLET;  
  11.     event.data.fd = fd;  
  12.     if( epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1 )  
  13.         err_exit("epoll_ctl_add error");  
  14. }  
  15. //从epoll删除fd  
  16. void delFd(int epollfd, int fd)  
  17. {  
  18.     struct epoll_event event;  
  19.     event.data.fd = fd;  
  20.     if( epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event) == -1 )  
  21.         err_exit("epoll_ctl_del error");  
  22. }  
  23.   
  24. int main()  
  25. {  
  26.     signal(SIGPIPE, sigHandlerForSigPipe);  
  27.     try  
  28.     {  
  29.         TCPServer server(8001);  
  30.         int listenfd = server.getfd();  
  31.         int epollfd = epoll_create1(EPOLL_CLOEXEC);  
  32.         if (epollfd == -1)  
  33.             err_exit("epoll_create1 error");  
  34.         // 将监听套接字注册到epoll  
  35.         addFd(epollfd, listenfd, EPOLLIN, true);  
  36.   
  37.         // 用于保存epoll_wait返回事件数组  
  38.         std::vector<struct epoll_event> events(16);  
  39.         char buf[BUFSIZ];  
  40.         int count = 0;  
  41.         while (true)  
  42.         {  
  43.             // 等待epoll返回  
  44.             int nReady = epoll_wait(epollfd, &*events.begin(), (int)events.size(), -1);  
  45.             if (nReady == -1)  
  46.             {  
  47.                 if (errno == EINTR)  
  48.                     continue;  
  49.                 err_exit("epoll_wait error");  
  50.             }  
  51.             if ((size_t)nReady == events.size())  
  52.                 events.resize(events.size()*2);  
  53.   
  54.             for (int i = 0; i < nReady; ++i)  
  55.             {  
  56.                 // 如果是监听套接字发送了可读事件  
  57.                 if (events[i].data.fd == listenfd)  
  58.                 {  
  59.                     int connectfd = accept(listenfd, NULL, NULL);  
  60.                     if (connectfd == -1)  
  61.                         err_exit("accept error");  
  62.                     cout << "accept success..." << endl;  
  63.                     cout << "count = " << ++count << endl;  
  64.                     setUnBlock(connectfd, true);  
  65.                     addFd(epollfd, connectfd, EPOLLIN, true);  
  66.                 }  
  67.                 // 如果是已连接套接字发生了可读事件  
  68.                 else if (events[i].events & EPOLLIN)  
  69.                 {  
  70.                     int connectfd = events[i].data.fd;  
  71.                     if (connectfd < 0)  
  72.                         continue;  
  73.   
  74.                     memset(buf, 0, sizeof(buf));  
  75.                     int ret = readline(connectfd, buf, sizeof(buf)-1);  
  76.                     if (ret == -1)  
  77.                         err_exit("read-line error");  
  78.                     // 如果对端关闭  
  79.                     else if (ret == 0)  
  80.                     {  
  81.                         cerr << "client connect closed..." << endl;  
  82.                         // 将该套接字同epoll中移除  
  83.                         delFd(epollfd, connectfd);  
  84.                         close(connectfd);  
  85.                         continue;  
  86.                     }  
  87.                     cout << buf;  
  88.                     writen(connectfd, buf, strlen(buf));  
  89.                 }  
  90.             }  
  91.         }  
  92.     }  
  93.     catch (const SocketException &e)  
  94.     {  
  95.         cerr << e.what() << endl;  
  96.         err_exit("TCPServer error");  
  97.     }  
  98. }  

小结-epoll与select、poll的区别

1.相比于select与poll, epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。

   因为内核中select/poll的实现是采用轮询来处理的, 因此他们检测就绪实践的算法时间复杂度是O(N), 因此, 需要轮询的fd数目越多, 自然耗时越多, 他们的性能呈线性甚至指数的方式下降。

   而epoll的实现是基于事件回调的,如果fd有期望的事件发生就通过回调函数将其加入epoll就绪队列中,也就是说它只关心“活跃”的fd,与fd数目无关 其算法时间复杂度为O(1)。

2. 内核空间与用户空间内存拷贝问题,如何让内核把 fd消息通知给用户空间呢?在这个问题上select/poll采取了内存拷贝方法。而epoll采用了内核和用户空间共享内存的方式。

3. epoll不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。而select/poll模型,当有 I/O 事件到来时, select/poll通知应用程序有事件到达,而应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件。

4. 当活动连接比较多的时候, epoll_wait的效率就未必比select/poll高了, 因为这时候对于epoll 来说一直在调用callback 函数, 回调函数被触发得过于频繁, 所以epoll_wait适用于连接数量多, 但活动连接少的情况;

 

ET/LT模式

1、EPOLLLT:完全靠Linux-kernel-epoll驱动,应用程序只需要处理从epoll_wait返回的fds, 这些fds我们认为它们处于就绪状态。此时epoll可以认为是更快速的poll。

2、EPOLLET:此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何状态信息(从epoll队列移除), 直到应用程序通过读写操作(非阻塞)触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列)。 随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发的系统中,EPOLLET更有优势,但是对程序员的要求也更高,因为有可能会出现数据读取不完整的问题,举例如下:

   假设现在对方发送了2k的数据,而我们先读取了1k,然后这时调用了epoll_wait,如果是边沿触发ET,那么这个fd变成就绪状态就会从epoll 队列移除,则epoll_wait 会一直阻塞,忽略尚未读取的1k数据; 而如果是水平触发LT,那么epoll_wait 还会检测到可读事件而返回,我们可以继续读取剩下的1k 数据。

   因此总结来说: LT模式可能触发的次数更多, 一旦触发的次数多, 也就意味着效率会下降; 但这样也不能就说LT模式就比ET模式效率更低, 因为ET的使用对编程人员提出了更高更精细的要求, 一旦编程人员水平达不到(比如本人), 那ET模式还不如LT模式;


Epoll-Class封装

   在本部分我们实现一个较为好用实用的Epoll并发类, 由于实现代码与使用方式较简单, 因此就不在此赘述了, 下面我还使用了该类实现了一个基于Epoll的echo-server, 以演示该类的用法;

   由于此处仅为Epoll类库的第一个版本, 因此错误之处必然会存在, 如果读者在阅读的过程中发现了该类库的BUG, 还望这篇博客的读者朋友不吝赐教; 而作者也会不断的更新该类库(主要更新代码我会发布到此处), 以处理新的业务需求;


Epoll类设计

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class Epoll  
  2. {  
  3. public:  
  4.     Epoll(int flags = EPOLL_CLOEXEC, int noFile = 1024);  
  5.     ~Epoll();  
  6.   
  7.     void addfd(int fd, uint32_t events = EPOLLIN, bool ETorNot = false);  
  8.     void modfd(int fd, uint32_t events = EPOLLIN, bool ETorNot = false);  
  9.     void delfd(int fd);  
  10.     int wait(int timeout = -1);  
  11.     int getEventOccurfd(int eventIndex) const;  
  12.     uint32_t getEvents(int eventIndex) const;  
  13.   
  14. public:  
  15.     bool isValid()  
  16.     {  
  17.         if (m_epollfd == -1)  
  18.             return false;  
  19.         return true;  
  20.     }  
  21.     void close()  
  22.     {  
  23.         if (isValid())  
  24.         {  
  25.             :: close(m_epollfd);  
  26.             m_epollfd = -1;  
  27.         }  
  28.     }  
  29.   
  30. private:  
  31.     std::vector<struct epoll_event> events;  
  32.     int m_epollfd;  
  33.     int fdNumber;  
  34.     int nReady;  
  35. private:  
  36.     struct epoll_event event;  
  37. };  

Epoll类实现

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** epoll_create **/  
  2. Epoll::Epoll(int flags, int noFile) : fdNumber(0), nReady(0)  
  3. {  
  4.     struct rlimit rlim;  
  5.     rlim.rlim_cur = rlim.rlim_max = noFile;  
  6.     if ( ::setrlimit(RLIMIT_NOFILE, &rlim) == -1 )  
  7.         throw EpollException("setrlimit error");  
  8.   
  9.     m_epollfd = ::epoll_create1(flags);  
  10.     if (m_epollfd == -1)  
  11.         throw EpollException("epoll_create1 error");  
  12. }  
  13. Epoll::~Epoll()  
  14. {  
  15.     this -> close();  
  16. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** epoll_ctl **/  
  2. void Epoll::addfd(int fd, uint32_t events, bool ETorNot)  
  3. {  
  4.     bzero(&event, sizeof(event));  
  5.     event.events = events;  
  6.     if (ETorNot)  
  7.         event.events |= EPOLLET;  
  8.     event.data.fd = fd;  
  9.     if( ::epoll_ctl(m_epollfd, EPOLL_CTL_ADD, fd, &event) == -1 )  
  10.         throw EpollException("epoll_ctl_add error");  
  11.     ++ fdNumber;  
  12. }  
  13. void Epoll::modfd(int fd, uint32_t events, bool ETorNot)  
  14. {  
  15.     bzero(&event, sizeof(event));  
  16.     event.events = events;  
  17.     if (ETorNot)  
  18.         event.events |= EPOLLET;  
  19.     event.data.fd = fd;  
  20.     if( ::epoll_ctl(m_epollfd, EPOLL_CTL_MOD, fd, &event) == -1 )  
  21.         throw EpollException("epoll_ctl_mod error");  
  22. }  
  23. void Epoll::delfd(int fd)  
  24. {  
  25.     bzero(&event, sizeof(event));  
  26.     event.data.fd = fd;  
  27.     if( ::epoll_ctl(m_epollfd, EPOLL_CTL_DEL, fd, &event) == -1 )  
  28.         throw EpollException("epoll_ctl_del error");  
  29.     -- fdNumber;  
  30. }  
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** epoll_wait **/  
  2. int Epoll::wait(int timeout)  
  3. {  
  4.     events.resize(fdNumber);  
  5.     while (true)  
  6.     {  
  7.         nReady = epoll_wait(m_epollfd, &*events.begin(), fdNumber, timeout);  
  8.         if (nReady == 0)  
  9.             throw EpollException("epoll_wait timeout");  
  10.         else if (nReady == -1)  
  11.         {  
  12.             if (errno == EINTR)  
  13.                 continue;  
  14.             else  throw EpollException("epoll_wait error");  
  15.         }  
  16.         else  
  17.             return nReady;  
  18.     }  
  19.     return -1;  
  20. }  
  21.   
  22. int Epoll::getEventOccurfd(int eventIndex) const  
  23. {  
  24.     if (eventIndex > nReady)  
  25.         throw EpollException("parameter(s) error");  
  26.     return events[eventIndex].data.fd;  
  27. }  
  28. uint32_t Epoll::getEvents(int eventIndex) const  
  29. {  
  30.     if (eventIndex > nReady)  
  31.         throw EpollException("parameter(s) error");  
  32.     return events[eventIndex].events;  
  33. }  

使用Epoll的echoserver(测试)代码:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3. signal(SIGPIPE, SIG_IGN);  
  4.     /** 
  5.     将下面的这两个变量设置成为放在程序的开头, 
  6.     只是因为这样可以使得业务处理部分的代码显 
  7.     得简洁一些,在实际应用(C++)中,没必要也不 
  8.     推荐这样使用 
  9.     **/  
  10.     char buf[BUFSIZ];  
  11.     int clientCount = 0;  
  12.     try  
  13.     {  
  14.         TCPServer server(8001);  
  15.         int listenfd = server.getfd();  
  16.         Epoll epoll;  
  17.         // 将监听套接字注册到epoll  
  18.         epoll.addfd(server.getfd(), EPOLLIN, true);  
  19.         while (true)  
  20.         {  
  21.             int nReady = epoll.wait();  
  22.             for (int i = 0; i < nReady; ++i)  
  23.                 // 如果是监听套接字发生了可读事件  
  24.                 if (epoll.getEventOccurfd(i) == listenfd)  
  25.                 {  
  26.                     int connectfd = accept(listenfd, NULL, NULL);  
  27.                     if (connectfd == -1)  
  28.                         err_exit("accept error");  
  29.                     cout << "accept success..." << endl;  
  30.                     cout << "clientCount = " << ++ clientCount << endl;  
  31.                     setUnBlock(connectfd, true);  
  32.                     epoll.addfd(connectfd, EPOLLIN, true);  
  33.                 }  
  34.                 else if (epoll.getEvents(i) & EPOLLIN)  
  35.                 {  
  36.                     TCPClient *client = new TCPClient(epoll.getEventOccurfd(i));  
  37.                     memset(buf, 0, sizeof(buf));  
  38.                     if (client->read(buf, sizeof(buf)) == 0)  
  39.                     {  
  40.                         cerr << "client connect closed..." << endl;  
  41.                         // 将该套接字从epoll中移除  
  42.                         epoll.delfd(client->getfd());  
  43.                         delete client;  
  44.                         continue;  
  45.                     }  
  46.                     cout << buf;  
  47.                     client->write(buf);  
  48.                 }  
  49.         }  
  50.     }  
  51.     catch (const SocketException &e)  
  52.     {  
  53.         cerr << e.what() << endl;  
  54.         err_exit("TCPServer error");  
  55.     }  
  56.     catch (const EpollException &e)  
  57.     {  
  58.         cerr << e.what() << endl;  
  59.         err_exit("Epoll error");  
  60.     }  
  61. }  

完整源代码请参照:

http://download.csdn.net/detail/hanqing280441589/8492911


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值