作者: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
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 64EpollServer::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 = appTARGET = DEPENDPATH += .INCLUDEPATH += .CONFIG -= qtPRECOMPILED_HEADER += stdafx.h# InputSOURCES += main.cpp epollserver.cppHEADERS += epollserver.h
編譯方法:
qmake epoll.promake
如果這篇文章對您有協助,請到CSDN部落格留言; 轉載請註明:來自雨潤的技術部落格 http://blog.csdn.net/sunyurun