epoll是Linux提供的高级轮询技术,《UNIX网络编程 卷1》成书时还没有这种技术。它的效率要比单独使用poll函数高很多,非常适合监听大量的描述符,许多高性能的服务器都使用epoll。下面是epoll提供的API,具体使用请参考下面的代码和man手册。
/*创建一个epoll实例,返回其描述符*/
int epoll_create(int size);
int epoll_create1(int flags);
/*epoll实例的控制接口,比如添加描述符等*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*等待一个epoll实例上I/O事件发生*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
本节我们使用epoll来实现TCP回显服务器程序,代码如下:
#include "unp.h"
#define MAX_EVENTS 1024
int main(int argc, char **argv)
{
int epollfd, listenfd, connfd, sockfd;
int i, nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
struct epoll_event ev, events[MAX_EVENTS];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
epollfd = epoll_create1(0); /*创建一个epoll实例*/
if (epollfd == -1) {
err_sys("epoll_create1");
}
/*将服务器监听描述符加入epoll实例监听*/
ev.events = EPOLLIN;
ev.data.fd = listenfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
err_sys("epoll_ctl: listenfd");
}
for ( ; ; ) {
nready = epoll_wait(epollfd, events, MAX_EVENTS, -1); /*等待I/O事件发生*/
if (nready == -1) {
err_sys("epoll_wait");
}
for (i = 0; i < nready; i++) {
if (events[i].data.fd == listenfd) { /*有新的连接*/
connfd = Accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
/*将客户套接字描述符设置为非阻塞*/
Fcntl(connfd, F_SETFL, O_NONBLOCK);
/*将客户套接字描述符加入epoll实例监听*/
ev.events = EPOLLIN | EPOLLET; /*边沿触发方式工作*/
ev.data.fd = connfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) == -1) {
err_sys("epoll_ctl: connfd");
}
} else { /*有新的数据到达*/
sockfd = events[i].data.fd;
if ((n = read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { /*客户异常关闭连接*/
/*从epoll实例监听的描述符中移除客户套接字描述符*/
if (epoll_ctl(epollfd, EPOLL_CTL_DEL, sockfd, NULL) == -1) {
err_sys("epoll_ctl: sockfd");
}
Close(sockfd);
} else
err_sys("read error");
} else if (n == 0) { /*客户正常关闭连接*/
if (epoll_ctl(epollfd, EPOLL_CTL_DEL, sockfd, NULL) == -1) {
err_sys("epoll_ctl: sockfd");
}
Close(sockfd);
} else
Writen(sockfd, buf, n);
}
}
}
}
之前我们使用poll函数实现的版本在这里。抛开底层实现不谈,对本例而言,我们单纯从代码的角度就可以看出epoll技术优于使用poll函数的轮询技术。