epoll实例2

xiaoxiao2025-07-16  7

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 struct epoll_event {     uint32_t     events;      /* Epoll events */     epoll_data_t data;        /* User data variable */ }; 参数events:

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

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

EPOLLOUT:适用write操作

EPOLLRDHUP :TCP对端关闭了连接

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

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

EPOLLHUP:与上边EPOLLERR相同

EPOLLET:设置边缘触发方式

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

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

5. API详解 5.1 epoll_create int epoll_create(int size); 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 #ifndef STDAFX_H #define STDAFX_H   #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <fcntl.h> #include <sys/epoll.h> #include <errno.h>   #endif // STDAFX_H 6.2 epollserver.h #ifndef EPOLLSERVER_H #define EPOLLSERVER_H   /**  * @brief The EpollServer class  * @author yurunsun@gmail.com  */   class EpollServer { public:     EpollServer();     ~EpollServer();     bool _listen(const std::string &port);     bool pulse();   private:     bool setUnblock(int socket);     bool createEpoll();     bool addEpoll(int socket, epoll_event &e);     bool isEpollError(const epoll_event& e);     bool isEpollNewConnection(const epoll_event& e);     bool _error(const epoll_event& e);     bool _accept(epoll_event &e);     bool _receive(const epoll_event& e);     bool _send(int clientFd, const std::string& data);     bool removeClient(int clientFd);       addrinfo m_serverAddr;              /** server address */     int m_listenerSocket;               /** listening socket descriptor */     int m_epollFd;                      /** epoll operation fd */     epoll_event m_epollEvent;           /** epoll event*/     epoll_event* m_pEpollEvents;        /** epoll events buffer to hold notification from kernal*/     char m_readBuf[1024];               /** buffer for client data */ };   #endif // EPOLLSERVER_H 6.3 epollserver.cpp #include "stdafx.h" #include "epollserver.h"   using namespace std; #define MAXEVENTS 64   EpollServer::EpollServer()     : m_pEpollEvents(NULL) { }   EpollServer::~EpollServer() {     if (m_pEpollEvents != NULL) {         delete [] m_pEpollEvents;     } }   bool EpollServer::_listen(const string& port) {     cout << "try to listen port " << port << endl;     addrinfo *pResult = NULL;     memset(&(m_serverAddr), '\0', sizeof(m_serverAddr));     m_serverAddr.ai_family = AF_UNSPEC;         /** Return IPv4 and IPv6 choices */     m_serverAddr.ai_socktype = SOCK_STREAM;     /** We want a TCP socket */     m_serverAddr.ai_flags = AI_PASSIVE;         /** All interfaces */       if (getaddrinfo(NULL, port.c_str(), &m_serverAddr, &pResult) != 0) {         cerr << "fail to getaddrinfo!" << endl;         return false;     }     if (pResult != NULL) {         for (addrinfo *pRes = pResult; pRes != NULL; pRes = pRes->ai_next) {             if ((m_listenerSocket = socket (pRes->ai_family, pRes->ai_socktype, pRes->ai_protocol)) == -1) {                 cerr << "fail to create socket for " << pRes->ai_family << " " << pRes->ai_socktype << " " << pRes->ai_protocol << endl;                 continue;             }             if (bind(m_listenerSocket, pRes->ai_addr, pRes->ai_addrlen) == -1) {                 cerr << "fail to bind " << m_listenerSocket << " " << pRes->ai_addr << " " << pRes->ai_addrlen << endl;                 close(m_listenerSocket);                 continue;             }             freeaddrinfo(pResult);             setUnblock(m_listenerSocket);             if (listen (m_listenerSocket, SOMAXCONN) == -1) {                 cerr << "fail to listen " << m_listenerSocket << endl;             } else {                 cout << "listen port " << port << " ok! " << endl;                 return createEpoll();                    /** We managed to bind successfully! */             }         }     }     return false; }   bool EpollServer::pulse() {     int n = epoll_wait(m_epollFd, m_pEpollEvents, MAXEVENTS, -1);     for (int i = 0; i < n; ++i) {         epoll_event& e = m_pEpollEvents[i];         if (isEpollError(e)) {             _error(e);         } else if (isEpollNewConnection(e)) {             _accept(e);         } else {             _receive(e);         }     }     return true; }   bool EpollServer::setUnblock(int socket) {     int flag = 0;     if ((flag = fcntl(socket, F_GETFL, 0)) != -1) {         flag |= O_NONBLOCK;         if (fcntl (socket, F_SETFL, flag) != -1) {             return true;         }     }     cerr << "fail to call fcntl F_SETFL for m_listenerSocket" << endl;     return false; }   bool EpollServer::createEpoll() {     cout << "try to creat epoll" << endl;     if ((m_epollFd = epoll_create1(0)) == -1) {         cerr << "fail to call epoll_create" << endl;         return false;     }     m_epollEvent.data.fd = m_listenerSocket;     m_epollEvent.events = EPOLLIN | EPOLLET;     if (addEpoll(m_listenerSocket, m_epollEvent)) {         m_pEpollEvents = new epoll_event[MAXEVENTS];         cout << "create epoll ok!" << endl;         return true;     }     return false; }   bool EpollServer::addEpoll(int socket, epoll_event& e) {     if ((epoll_ctl (m_epollFd, EPOLL_CTL_ADD, socket, &e)) == -1) {         cerr << "fail to call epoll_ctl for " << socket << endl;         return false;     }     return true; }   bool EpollServer::isEpollError(const epoll_event &e) {     return ((e.events & EPOLLERR) || (e.events & EPOLLHUP) || (!(e.events & EPOLLIN))); }   bool EpollServer::isEpollNewConnection(const epoll_event &e) {     return (m_listenerSocket == e.data.fd); }   bool EpollServer::_error(const epoll_event &e) {     /** An error has occured on this fd, or the socket is not ready for reading */     cerr << "epoll error for client " << e.data.fd << endl;     removeClient(e.data.fd);     return true; }   bool EpollServer::_accept(epoll_event &e) {     cout << "a new client is coming - " << e.data.fd << endl;     sockaddr clientAddr;     int clientFd = 0;     socklen_t clientAddrLen = sizeof (clientAddr);     char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];       if ((clientFd = accept (m_listenerSocket, &clientAddr, &clientAddrLen)) == -1) {         if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {             cerr << "fail to accept new client " << endl;             return false;         }     }     if (getnameinfo (&clientAddr, clientAddrLen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == -1) {         cerr << "fail to getnameinfo" << endl;     }     if (!setUnblock(clientFd)) {         cerr << "fail to set unblock to client fd" << clientFd << endl;         return false;     }     e.data.fd = clientFd;     e.events = EPOLLIN | EPOLLET;     return addEpoll(clientFd, e); }   bool EpollServer::_receive(const epoll_event &e) {     int clientFd = e.data.fd;     uint32_t nbytes = recv(clientFd, m_readBuf, sizeof(m_readBuf), 0);     cout << "receive " << nbytes << " bytes data from client " << clientFd << endl;     if (nbytes > 0) {   /** we got some data from a client*/         string data(m_readBuf, nbytes);         _send(1, data);         _send(clientFd, data);     } else {         cout << "socket " << clientFd << " has sth wrong since nbytes == " << nbytes  << endl;         removeClient(clientFd);     }     return true; }   bool EpollServer::_send(int clientFd, const std::string &data) {     if (write(clientFd, data.c_str(), data.size()) == -1) {         cerr << "fail to send data to " << clientFd << endl;         return false;     }     return true; }   bool EpollServer::removeClient(int clientFd) {     cout << "remove client " << clientFd << endl;     close (clientFd);     return true; } 6.4 main.cpp #include "stdafx.h" #include "epollserver.h"   using namespace std;   int main(int argc, char* argv[]) {     if (argc >= 2) {         EpollServer server;         if (server._listen(argv[1])) {             while (server.pulse()) {                 usleep(1000);             }         }     } else {         cout << "Usage: [port]" << endl;     }     return 0; } 6.5 qmake工程文件epoll.pro ###################################################################### # Automatically generated by qmake (2.01a) Fri Nov 16 18:01:16 2012 ######################################################################   TEMPLATE = app TARGET =  DEPENDPATH += . INCLUDEPATH += . CONFIG -= qt PRECOMPILED_HEADER += stdafx.h   # Input SOURCES += main.cpp epollserver.cpp HEADERS += epollserver.h 编译方法:

qmake epoll.pro make

---------------------  作者:sunyurun  来源:  原文:https://blog.csdn.net/sunyurun/article/details/8194979  版权声明:本文为博主原创文章,转载请附上博文链接!

转载请注明原文地址: https://www.6miu.com/read-5033168.html

最新回复(0)