作者:yurunsun@gmail.com 新浪微博@孙雨润 新浪博客 优快云博客日期: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
6.2 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 */
-
};
-
-
6.3 epollserver.cpp
-
-
-
-
using namespace std;
-
-
-
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
-
-
-
-
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