深入linux网络编程(三):异步阻塞IO —— epoll

作者:yurunsun@gmail.com 新浪微博@孙雨润 新浪博客 CSDN博客日期:2012年11月17日

1. epoll的优越性

上一节介绍的select有几个缺点:

  • 存在最多监听的描述符上限FD_SETSIZE
  • 每次被唤醒时必须遍历才能知道是哪个描述符上状态ready,CPU随描述符数量线性增长
  • 描述符集需要从内核copy到用户态

这几个缺点反过来正是epoll的优点,或者说epoll就是为了解决这些问题诞生的:

  • 没有最多监听的描述符上限FD_SETSIZE,只受最多文件描述符的限制,在系统中可以使用ulimit -n设置,运维一般会将其设置成20万以上
  • 每次被唤醒时返回的是所有ready的描述符,同时还带有ready的类型
  • 内核态与用户态共享内存,不需要copy

2. 简述epoll的工作过程

2.1 创建

首先由epoll_create创建epoll的实例,返回一个用来标识此实例的文件描述符。

2.2 控制

通过epoll_ctl注册感兴趣的文件描述符,这些文件描述符的集合也被称为epoll set

2.3 阻塞

最后调用epoll_wait阻塞等待内核通知。

3. 水平触发(LB)和边缘触发(EB)

epoll的内核通知机制有水平触发和边缘触发两种表现形式,我们在下面例子中看一下两者的区别。

  • 有一个代表读的文件描述符(rfd)注册在epoll

  • 在管道的写端,写者写入了2KB数据

  • 调用epoll_wait会返回rfd作为ready的文件描述符

  • 管道读端从rfd读取了1KB数据

  • 再次调用epoll_wait

如果rfd文件描述符以ET的方式加入epoll的描述符集,那么上边最后一步就会继续阻塞,尽管rfd上还有剩余的数据没有读完。相反LT模式下,文件描述符上数据没有读完就会一直通知下去。

4. epoll的两个数据结构

4.1 epoll_event

  1. struct epoll_event {
  2. uint32_t events; /* Epoll events */
  3. epoll_data_t data; /* User data variable */
  4. };

参数events

此参数是一个位集合,可能有以下几种中的组合:

  • EPOLLIN:适用read操作,包括对端正常关闭

  • EPOLLOUT:适用write操作

  • EPOLLRDHUP :TCP对端关闭了连接

  • EPOLLPRI:对于read操作有紧急的数据到来

  • EPOLLERR:文件描述符上的错误,不需要设置在events上,因为epoll总是会等待错误

  • EPOLLHUP:与上边EPOLLERR相同

  • EPOLLET:设置边缘触发方式

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

4.2 epoll_data

  1. typedef union epoll_data {
  2. void *ptr;
  3. int fd;
  4. uint32_t u32;
  5. uint64_t u64;
  6. } epoll_data_t;

这个结构体有些tricky,是四种不同数据类型的union,实际上设计者的意思是内容是什么交给使用者决定,相当于一个上下文。一般使用int fd来区分是哪个socket发生的事件。

5. API详解

5.1 epoll_create

  1. int epoll_create(int size);
  2. int epoll_create1(int flags);

epoll_create创建了一个epoll的实例,请求内核为size大小的文件描述符分配一个事件通知对象。实际上size只是一个提示,并没有什么实际的作用。此函数返回用来标识epoll实例的文件描述符,此后所有对epoll的请求都要通过这个文件描述符。当不需要使用epoll时需要使用close关闭这个文件描述符,告诉内核销毁实例。

5.2 epoll_ctl

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

epoll实例epfd上的控制操作,op的取值有以下三种:

  • EPOLL_CTL_ADD: 将fd带着事件参数event注册到epfd

  • EPOLL_CTL_MOD: 改变事件

  • EPOLL_CTL_DEL: 从epfd上删除

返回的错误码请参阅man手册。

5.3 epoll_wait

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

等待注册在epfd上的事件,事件再events参数中带出。对于timeout参数:

  • -1:永远等待
  • 0:不等待直接返回
  • 其他:在超时时间内没有事件发生,返回0

6. 完整C++代码示例

与上节一样,网上的epoll代码基本都是已C语言方式写在同一个main函数中,本人实现了一个完全正确的、可读性好的版本。除了将select替换成了epoll,其他细节还有些变化,例如将sockaddr_in替换成了现代气息的addrinfo,支持ipv4/ipv6等等。

上一节中按照工程习惯,在SelectServer类中增加了虚函数回掉接口,供派生类实现。有读者反应会冲淡主题,在这个EpollServer中没再继续设计虚函数接口。猛击此处下载源码

6.1 stdafx.h

  1. #ifndef STDAFX_H
  2. #define STDAFX_H
  3. #include <cstdio>
  4. #include <cstdlib>
  5. #include <cstring>
  6. #include <string>
  7. #include <iostream>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <netdb.h>
  11. #include <unistd.h>
  12. #include <fcntl.h>
  13. #include <sys/epoll.h>
  14. #include <errno.h>
  15. #endif // STDAFX_H

6.2 epollserver.h

  1. #ifndef EPOLLSERVER_H
  2. #define EPOLLSERVER_H
  3. /**
  4. * @brief The EpollServer class
  5. * @author yurunsun@gmail.com
  6. */
  7. class EpollServer
  8. {
  9. public:
  10. EpollServer();
  11. ~EpollServer();
  12. bool _listen( const std:: string &port);
  13. bool pulse();
  14. private:
  15. bool setUnblock(int socket);
  16. bool createEpoll();
  17. bool addEpoll(int socket, epoll_event &e);
  18. bool isEpollError(const epoll_event& e);
  19. bool isEpollNewConnection(const epoll_event& e);
  20. bool _error( const epoll_event& e);
  21. bool _accept(epoll_event &e);
  22. bool _receive( const epoll_event& e);
  23. bool _send( int clientFd, const std:: string& data);
  24. bool removeClient(int clientFd);
  25. addrinfo m_serverAddr; /** server address */
  26. int m_listenerSocket; /** listening socket descriptor */
  27. int m_epollFd; /** epoll operation fd */
  28. epoll_event m_epollEvent; /** epoll event*/
  29. epoll_event* m_pEpollEvents; /** epoll events buffer to hold notification from kernal*/
  30. char m_readBuf[ 1024]; /** buffer for client data */
  31. };
  32. #endif // EPOLLSERVER_H

6.3 epollserver.cpp

  1. #include "stdafx.h"
  2. #include "epollserver.h"
  3. using namespace std;
  4. #define MAXEVENTS 64
  5. EpollServer::EpollServer()
  6. : m_pEpollEvents( NULL)
  7. {
  8. }
  9. EpollServer::~EpollServer()
  10. {
  11. if (m_pEpollEvents != NULL) {
  12. delete [] m_pEpollEvents;
  13. }
  14. }
  15. bool EpollServer::_listen( const string& port)
  16. {
  17. cout << "try to listen port " << port << endl;
  18. addrinfo *pResult = NULL;
  19. memset(&(m_serverAddr), '\0', sizeof(m_serverAddr));
  20. m_serverAddr.ai_family = AF_UNSPEC; /** Return IPv4 and IPv6 choices */
  21. m_serverAddr.ai_socktype = SOCK_STREAM; /** We want a TCP socket */
  22. m_serverAddr.ai_flags = AI_PASSIVE; /** All interfaces */
  23. if (getaddrinfo( NULL, port.c_str(), &m_serverAddr, &pResult) != 0) {
  24. cerr << "fail to getaddrinfo!" << endl;
  25. return false;
  26. }
  27. if (pResult != NULL) {
  28. for (addrinfo *pRes = pResult; pRes != NULL; pRes = pRes->ai_next) {
  29. if ((m_listenerSocket = socket (pRes->ai_family, pRes->ai_socktype, pRes->ai_protocol)) == -1) {
  30. cerr << "fail to create socket for " << pRes->ai_family << " " << pRes->ai_socktype << " " << pRes->ai_protocol << endl;
  31. continue;
  32. }
  33. if (bind(m_listenerSocket, pRes->ai_addr, pRes->ai_addrlen) == -1) {
  34. cerr << "fail to bind " << m_listenerSocket << " " << pRes->ai_addr << " " << pRes->ai_addrlen << endl;
  35. close(m_listenerSocket);
  36. continue;
  37. }
  38. freeaddrinfo(pResult);
  39. setUnblock(m_listenerSocket);
  40. if (listen (m_listenerSocket, SOMAXCONN) == -1) {
  41. cerr << "fail to listen " << m_listenerSocket << endl;
  42. } else {
  43. cout << "listen port " << port << " ok! " << endl;
  44. return createEpoll(); /** We managed to bind successfully! */
  45. }
  46. }
  47. }
  48. return false;
  49. }
  50. bool EpollServer::pulse()
  51. {
  52. int n = epoll_wait(m_epollFd, m_pEpollEvents, MAXEVENTS, -1);
  53. for ( int i = 0; i < n; ++i) {
  54. epoll_event& e = m_pEpollEvents[i];
  55. if (isEpollError(e)) {
  56. _error(e);
  57. } else if (isEpollNewConnection(e)) {
  58. _accept(e);
  59. } else {
  60. _receive(e);
  61. }
  62. }
  63. return true;
  64. }
  65. bool EpollServer::setUnblock( int socket)
  66. {
  67. int flag = 0;
  68. if ((flag = fcntl(socket, F_GETFL, 0)) != -1) {
  69. flag |= O_NONBLOCK;
  70. if (fcntl (socket, F_SETFL, flag) != -1) {
  71. return true;
  72. }
  73. }
  74. cerr << "fail to call fcntl F_SETFL for m_listenerSocket" << endl;
  75. return false;
  76. }
  77. bool EpollServer::createEpoll()
  78. {
  79. cout << "try to creat epoll" << endl;
  80. if ((m_epollFd = epoll_create1( 0)) == -1) {
  81. cerr << "fail to call epoll_create" << endl;
  82. return false;
  83. }
  84. m_epollEvent.data.fd = m_listenerSocket;
  85. m_epollEvent.events = EPOLLIN | EPOLLET;
  86. if (addEpoll(m_listenerSocket, m_epollEvent)) {
  87. m_pEpollEvents = new epoll_event[MAXEVENTS];
  88. cout << "create epoll ok!" << endl;
  89. return true;
  90. }
  91. return false;
  92. }
  93. bool EpollServer::addEpoll( int socket, epoll_event& e)
  94. {
  95. if ((epoll_ctl (m_epollFd, EPOLL_CTL_ADD, socket, &e)) == -1) {
  96. cerr << "fail to call epoll_ctl for " << socket << endl;
  97. return false;
  98. }
  99. return true;
  100. }
  101. bool EpollServer::isEpollError( const epoll_event &e)
  102. {
  103. return ((e.events & EPOLLERR) || (e.events & EPOLLHUP) || (!(e.events & EPOLLIN)));
  104. }
  105. bool EpollServer::isEpollNewConnection( const epoll_event &e)
  106. {
  107. return (m_listenerSocket == e.data.fd);
  108. }
  109. bool EpollServer::_error( const epoll_event &e)
  110. {
  111. /** An error has occured on this fd, or the socket is not ready for reading */
  112. cerr << "epoll error for client " << e.data.fd << endl;
  113. removeClient(e.data.fd);
  114. return true;
  115. }
  116. bool EpollServer::_accept(epoll_event &e)
  117. {
  118. cout << "a new client is coming - " << e.data.fd << endl;
  119. sockaddr clientAddr;
  120. int clientFd = 0;
  121. socklen_t clientAddrLen = sizeof (clientAddr);
  122. char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
  123. if ((clientFd = accept (m_listenerSocket, &clientAddr, &clientAddrLen)) == -1) {
  124. if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
  125. cerr << "fail to accept new client " << endl;
  126. return false;
  127. }
  128. }
  129. if (getnameinfo (&clientAddr, clientAddrLen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == -1) {
  130. cerr << "fail to getnameinfo" << endl;
  131. }
  132. if (!setUnblock(clientFd)) {
  133. cerr << "fail to set unblock to client fd" << clientFd << endl;
  134. return false;
  135. }
  136. e.data.fd = clientFd;
  137. e.events = EPOLLIN | EPOLLET;
  138. return addEpoll(clientFd, e);
  139. }
  140. bool EpollServer::_receive( const epoll_event &e)
  141. {
  142. int clientFd = e.data.fd;
  143. uint32_t nbytes = recv(clientFd, m_readBuf, sizeof(m_readBuf), 0);
  144. cout << "receive " << nbytes << " bytes data from client " << clientFd << endl;
  145. if (nbytes > 0) { /** we got some data from a client*/
  146. string data(m_readBuf, nbytes);
  147. _send( 1, data);
  148. _send(clientFd, data);
  149. } else {
  150. cout << "socket " << clientFd << " has sth wrong since nbytes == " << nbytes << endl;
  151. removeClient(clientFd);
  152. }
  153. return true;
  154. }
  155. bool EpollServer::_send( int clientFd, const std:: string &data)
  156. {
  157. if (write(clientFd, data.c_str(), data.size()) == -1) {
  158. cerr << "fail to send data to " << clientFd << endl;
  159. return false;
  160. }
  161. return true;
  162. }
  163. bool EpollServer::removeClient( int clientFd)
  164. {
  165. cout << "remove client " << clientFd << endl;
  166. close (clientFd);
  167. return true;
  168. }

6.4 main.cpp

  1. #include "stdafx.h"
  2. #include "epollserver.h"
  3. using namespace std;
  4. int main(int argc, char* argv[])
  5. {
  6. if (argc >= 2) {
  7. EpollServer server;
  8. if (server._listen(argv[ 1])) {
  9. while (server.pulse()) {
  10. usleep( 1000);
  11. }
  12. }
  13. } else {
  14. cout << "Usage: [port]" << endl;
  15. }
  16. return 0;
  17. }

6.5 qmake工程文件epoll.pro

  1. ######################################################################
  2. # Automatically generated by qmake (2.01a) Fri Nov 16 18:01:16 2012
  3. ######################################################################
  4. TEMPLATE = app
  5. TARGET =
  6. DEPENDPATH += .
  7. INCLUDEPATH += .
  8. CONFIG -= qt
  9. PRECOMPILED_HEADER += stdafx.h
  10. # Input
  11. SOURCES += main.cpp epollserver.cpp
  12. HEADERS += epollserver.h

编译方法:

  1. qmake epoll .pro
  2. make


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值