poll和select相比最大优势在于可以管理更多的文件描述符。
epoll和poll相比最大的有时是速度更快,减少了大量文件描述符从内核到用户态的拷贝。
使用epoll的大体框架是
1 创建epoll instalce,得到epoll的文件描述符
调用epoll_create可以在内核创建一个epoll instance,返回这个instance的文件描述符。size参数已经被忽略了。
int epoll_create(int size);
int epoll_create1(int flags);
epoll_create1的参数flags值得一说。它可以取0或者FD_CLOEXEC。如果设置了FD_CLOEXEC,那么epoll_create1返回的文件描述符就会被字段设置FD_CLOEXEC标记。于是当进程以后调用fork的时候,epoll_create1所返回的文件描述符在子进程里是被关闭的,用不了。
例如
int epollfd = epoll_create1(0);
2 利用上面得到的epollfd,添加要监控的文件描述符
struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN|EPOLLET;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) goto err;
其中:
第一个参数是epoll_create返回的fd
第二个参数是要做的事情,包括:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
第三个参数是要监控的fd
第四个参数是struct epoll_event用来描述要监控哪些时间,以及当时间发生时传回哪些数据。
EPOLLIN 表示要监控文件描述符的可读性
epoll_event.data是一个union,除了设置fd,也可以当作void*来使用,执行自定义的数据。当文件描述符准备好时,epoll会把这个指针再“吐出来”。
3 设置好要监控的fd和事件后,就可以等待事件发生了。
struct epoll_event *evs;
evs = calloc(64, sizeof(ev));
while(1)
{
int n = epoll_wait(epollfd, evs, 64, -1);
...
第一个参数是epoll_create返回的文件描述符
第二个参数是提供给epoll_wait的struct epoll_event数组,epoll会把准备好的文件描述符和时间,以及之前ADD时自定义的数放到这个数组里。
第三个参数是数组长度
第四个参数是超时时间
返回值n是此次检测到已就绪的文件描述符数量。之后可以依次取出evs[0]到evs[n-1]来处理。
4 处理得到的struct epoll_event数组
对于数组的每个成员,需要先检查文件描述符发生的什么事件,有没有错误产生。
if(e->events & EPOLLERR || e->events & EPOLLHUP )
....
epoll和poll都是检测文件描述符的缓冲区有没有数据,来判断文件描述符是否准备好了。
但是epoll的事件触发方式更灵活,有边缘触发(ET)和水平触发(LT)两种。
这两个概念和数字电路里的一样。
边缘触发: 当状态从0->1的瞬间触发一次。即缓冲区从空变成有数据的时候返回。
水平触发: 状态为1时始终触发。即只要缓冲区有数据,就会返回。
epoll的默认行为是水平触发,此时的行为和poll一直,就是只要缓冲区有数据没读完,调用epoll就会立刻返回。
边缘触发的性能更好,但是有个问题,就是只当缓冲期从空变成有数据的那一刻触发一次,之后再调用epoll,它就会阻塞,一直等待下一次缓冲区由空到非空的事件。
因此如果需要采用性能更好的边缘触发模式,需要做到以下两点:
1 只支持非阻塞文件描述符,也就是说必须设置NONBLOCK标记。
int fl = 0;
int r = 0;
if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;
if((r = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;
2 必须确保每一次触发后,都要处理完缓冲区的全部数据。
通常用一个while(1)来解决。
由于已经将文件描述符设置为NONBLOCK了,因此可以用死循环。
如果返回-1,需要需要判断如果errno是EAGAIN,则是正常的,说明缓冲区的数据处理完了。如果是其它的errno,就是真的出错了。
while(1)
{
if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)
{
if(errno != EAGAIN) close(evs[i].data.fd);
break;
}
else if (*buf == EOF)
{
close(evs[i].data.fd);
break;
}
...
...
例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int check_epoll_err(struct epoll_event* ee)
{
if(ee == NULL) return -1;
if(
ee->events & EPOLLERR ||
ee->events & EPOLLHUP
)
{
return -1;
}
return 1;
}
int no_block(int fd, char* errbuf)
{
int fl = 0;
int r = 0;
if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;
if((r = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;
return 0;
err:
strcpy(errbuf, strerror(errno));
return errno * -1;
}
int do_accept(int sock, char* errbuf)
{
int ep = 0;
int r = 0;
struct epoll_event ev;
struct epoll_event *evs;
if(listen(sock, 1024) < 0) goto err;
if((ep = epoll_create1(0)) < 0) goto err;
ev.data.fd = sock;
ev.events = EPOLLIN|EPOLLET;
if(epoll_ctl(ep, EPOLL_CTL_ADD, sock, &ev) < 0) goto err;
evs = calloc(64, sizeof(ev));
int n, i;
struct sockaddr_in caddr;
socklen_t addrlen;
int cfd;
while(1)
{
n = epoll_wait(ep, evs, 64, -1);
for(i=0; i<n; i++)
{
if(check_epoll_err(&(evs[i])) < 0)
{
close(evs[i].data.fd);
continue;
}
else if(sock == evs[i].data.fd)
{
while(1) //loop try one non_block fd.
{
if((cfd = accept(sock, (struct sockaddr*)&caddr, &addrlen)) < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
break; // all connection accepted.
else
goto err;
}
printf("accept client: %s:%d\n", inet_ntoa(caddr.sin_addr), caddr.sin_port);
// make client fd non_block.
if((no_block(cfd, errbuf)) < 0) goto err;
// add client into epoll.
ev.data.fd = cfd;
ev.events = EPOLLIN|EPOLLET;
if(epoll_ctl(ep, EPOLL_CTL_ADD, cfd, &ev) < 0) goto err;
continue;
}
puts("accept ok");
}
else
{
// client fd ready.
if(evs[i].events & EPOLLIN == EPOLLIN)
{
int cnt;
char buf[BUFSIZ];
int finish = 0;
char hello[10] = "hello: ";
while(1)
{
if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)
{
if(errno != EAGAIN) close(evs[i].data.fd);
break;
}
else if (*buf == EOF)
{
close(evs[i].data.fd);
break;
}
printf("from client: %s", buf);
if(write(evs[i].data.fd, hello, strlen(hello)) < 0) goto err;
if(write(evs[i].data.fd, buf, cnt) < 0) goto err;
}
}
}
}
}
return 0;
err:
strcpy(errbuf, strerror(errno));
return errno * -1;
}
int do_listen(int port, char* errbuf)
{
int r = 0;
int sock = -1;
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "0.0.0.0", &servaddr.sin_addr);
servaddr.sin_port = htons(port);
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) goto err;
else if((r = bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) goto err;
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
// make client fd non_block.
if((no_block(sock, errbuf)) < 0) goto err;
return sock;
err:
strcpy(errbuf, strerror(errno));
return errno * -1;
}
int main()
{
int port = 1111;
int r = 0;
char errbuf[BUFSIZ];
int sock = do_listen(port, errbuf);
if(sock < 0)
{
puts(errbuf);
exit(errno);
}
r = do_accept(sock, errbuf);
if(r < 0)
{
puts(errbuf);
exit(errno);
}
return 0;
}
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26239116/viewspace-2075385/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/26239116/viewspace-2075385/