Epoll介绍
Epoll是当前大规模并发服务器编程的完美方案。据评测性能比select提高不只一个数量级。Linux从kernel 2.6之后开始支持。关于常用网络服务器编程模型的优缺点比较可以参考这里:http://blog.csdn.net/sparkliang/article/details/4770655
Epoll相比select主要的改进在于:epoll可以监听数量巨大的fd,至于多大主要受内存限制(而select监听的fd最大值一般为1024);另一个是效率方面的,select随着监听的fd数量的增加效率会降低,epoll则没有这个问题。
epoll_create()
#include <sys/epoll.h>
int epoll_create(int size);
epoll_create()创建一个epoll实例。从Linux 2.6.8开始,参数size被忽略,但必须大于0. epoll_create()返回一个指向新的epoll实例的文件描述符。该文件描述符被用于后续的一系列epoll接口的调用。当不再使用时,应使用close()把它关掉。
返回值:成功返回非负文件描述符,出错返回-1, 并设置errno指示出错类型。
epoll_ctl()
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
事件注册函数,注册要监听的事件类型。
epoll_ctl()系统调用在epfd所指向的epoll实例上执行控制操作。它请求在目标文件描述符fd上执行操作op.
参数op的有效值如下:
EPOLL_CTL_ADD
在epfd指向的epoll实例上注册目标文件描述符fd并监听fd上的事件event。
EPOLL_CTL_MOD
改变目标文件描述符fd的监听事件
EPOLL_CTL_DEL
将目标文件描述符fd从epfd所指向的epoll实例中删除。参数event可以被忽略,可以为NULL。
参数event告诉内核要监听什么事件。
epoll_event结构定义如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events成员是一个位集合,可以由以下可用事件组成。
EPOLLIN
侦听关联文件的读操作
EPOLLOUT
侦听关联文件的写操作
EPOLLRDHUP
流套接字对端关闭了连接,或关闭写一半的连接。
EPOLLPRI有紧急数据可读
EPOLLET
将epoll设备边缘触发模式
EPOLLERR
关联的文件描述符发生错误。poll_wait()总是等待这类事件,不需要被设置。
EPOLLHUP
关联的文件描述符被挂起。poll_wait()总是等待这类事件,不需要被设置。
EPOLLONESHOT
只监听一次事件,当监听完这次事件之后,如果还需要监听这个fd的话,需要再次把该fd注册到epoll中
返回值:成功,返回0,出错返回-1,并设置errno.
epoll_wait()
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
epoll_wait()系统调用等待epfd指向的epoll实例上的事件发生。events指向的内存区域包含了调用者可用的事件。最多有maxevents事件被返回。参数maxevents的值必须大于0.
epoll_wait会阻塞系统调用,阻塞的最大时间是timeout毫秒。设置timeout为-1将导致epoll_wait()无限期被阻塞直到有事件发生。设置timeout为0,将导致epoll_wait()立即返回。
epoll_wait每次返回的events结构中的data是用户通过调用epoll_ctl(EPOLL_CTL_ADD, EPOLL_CTL_MOD)设置的结构中的data是一样的。
返回值:
若成功,epoll_wait()返回准备好请求的I/O事件的描述符的数量,或者返回0表示没在请求的timeout毫秒内没有一个描述做好准备。出错返回-1.
epoll echo server
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
int open_listenfd(int port);
int echo(struct epoll_event *pev);
#define MAXLEN 512
int main(int argc, char **argv)
{
int port, listenfd, connfd, addrLen = sizeof(struct sockaddr_in);
struct sockaddr_in clientAddr;
if (argc != 2)
{
printf("usage: %s <port>\n", argv[0]);
return 0;
}
port = atoi(argv[1]);
if ( (listenfd = open_listenfd(port)) < 0)
{
perror("open_listenfd: ");
return 0;
}
int readepfd = epoll_create(1); //2.6.8之后size值被忽略,但要大于0
struct epoll_event tevent;
tevent.events = EPOLLIN;
tevent.data.fd = listenfd;
epoll_ctl(readepfd, EPOLL_CTL_ADD, listenfd, &tevent);
struct epoll_event ev;
while (1)
{
int n = epoll_wait(readepfd, &ev, 1, -1);
if (n>0)
{
if (ev.data.fd == listenfd)
{
connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);
printf("connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
struct epoll_event tev;
tev.data.fd = connfd;
tev.events = EPOLLIN;
epoll_ctl(readepfd, EPOLL_CTL_ADD, connfd, &tev);
}
else
{
if (ev.events & EPOLLIN)
{
if (echo(&ev) <= 0)
{
printf("close\n");
epoll_ctl(readepfd, EPOLL_CTL_DEL, ev.data.fd, NULL);
close(ev.data.fd);
}
}
}
}
}
close(listenfd);
close(readepfd);
}
int echo(struct epoll_event *pev)
{
char buff[MAXLEN];
int n;
n = recv(pev->data.fd, buff, MAXLEN, 0);
if (n > 0)
{
buff[n] = 0;
printf("%d recv: %s\n", pev->data.fd, buff);
}
return n;
}
int open_listenfd(int port)
{
struct sockaddr_in myAddr;
int listenfd;
/* Create a socket descriptor */
if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1;
/*
listenfd will be an end point for all requests to port on any IP address for this host
*/
bzero((char *)&myAddr, sizeof(myAddr));
myAddr.sin_family = AF_INET;
myAddr.sin_port = htons(port);
myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&myAddr, sizeof(myAddr)) < 0)
{
close(listenfd);
return -1;
}
if (listen(listenfd, 1024) < 0)
{
close(listenfd);
return -1;
}
return listenfd;
}
总结
▶ 尽量少的调用epoll_ctl接口,即想办法减少在EPOLLIN和EPOLLOUT之间的切换
▶ 使用非阻塞的fd和ET模式
▶ 使用事件缓存,循环在就绪的fds之间分配合理的时间,避免造成饥饿状态