简介
epoll是同poll相似的,用于同时监听多个fd上的相关I/O事件。epoll相较于poll而言有边缘触发和水平触发两种事件触发模式,poll只有水平触发一种事件触发模式。
事件触发模式介绍
水平触发
水平触发(Level-Triggered)是当被监控的fd上有可读写事件发生时,epoll_wait会通知程序去读写。如果程序在这次操作没有将数据全部读写完(可能由于缓冲区不够),那么下次调用epoll_wait时仍然会通知程序去读写,直到数据被读写完为止。
优点: 开发时每次读写操作只需要读写想要的数据量即可,这样可以每次事件触发执行一个操作。
缺点: 如果系统中有大量不需要读写的fd时,每次epoll_wait都会返回,效率会很低。
边缘触发
边缘触发(Edge-Triggered)是当被监控的fd上有可读写事件发生时,epoll_wait会通知程序去读写。如果程序在这次操作没有将数据全部读写完(可能由于缓冲区不够),那么下次调用epoll_wait时将不会再通知程序去读写(即:只通知一次)。直到该被监控fd上第二次出现可读写事件时,epoll_wait才会通知程序去读写。
优点: 如果系统中有大量不需要读写fd时,不用每次epoll_wait返回时都会返回,相比水平触发效率更高。
缺点: 如果没有一次性将数据全部读写完,则不会再收到事件通知。
相关结构体介绍
struct 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 */
};
event事件
EPOLLIN: 和fd相关联的读操作事件。
EPOLLOUT: 和fd关联的写操作事件。
EPOLLRDHUP: 和fd关联的读挂起事件。在流式socket的通信对端关闭了连接,或关闭了一半的写操作。
EPOLLPRI: 和fd关联的紧急数据的读操作事件。
EPOLLERR: 和fd关联的错误事件。
EPOLLHUP: 和fd关联的挂起事件。
EPOLLET: 设置fd触发方式为边缘触发。默认为水平触发。
EPOLLONESHOT: 设置fd的事件只触发一次。在一次epoll_wait报告该fd关联事件后,系统将禁止报告该fd关联的事件,必须通过epoll_ctl的EPOLL_CTL_MOD方法重新设置fd的事件触发。
依赖头文件
#include <sys/epoll.h>
相关函数介绍
epoll_create
int epoll_create(int size);
创建一个epoll实例,返回标识该epoll实例的fd。该fd将用于epoll_wait/epoll_pwait/epoll_ctl等操作的第一个参数。
参数
size: linux2.6.8后该参数将会被忽略,但是必须大于0。
返回值
成功返回一个非负的fd,该fd将于epoll_wait等接口。失败返回-1,错误码在errno中。
epoll_create1
int epoll_create1(int flags);
创建一个epoll实例,返回标识该epoll实例的fd。该fd将用于epoll_wait/epoll_pwait/epoll_ctl等操作的第一个参数。
参数
flags: 为0,则效果等同于epoll_create;为EPOLL_CLOEXEC,则会将fd设置为close-on-exec(即:在fd上设置FD_CLOEXEC标识)。
返回值
成功返回一个非负的fd,该fd将于epoll_wait等接口。失败返回-1,错误码在errno中。
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
对epoll实例执行相应的控制操作。
参数
epfd: 和epoll实例关联的fd。
op: 操作的操作码。取值有:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。
fd: 执行操作应用的fd。
event: 执行操作应用fd对应的事件信息。
返回值
成功返回0。失败返回-1,错误码在errno中。
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
等待epoll实例中关联的fd上的相关事件被触发,或超时时返回。
参数
epfd: 和epoll实例关联的fd。
events: struct epoll_event结构体数组,用于接收epoll关联fd的事件信息。
maxevents: events的数组大小。
timeout: 等待超时时间,单位毫秒。等于-1表示一直等待。
epoll_pwait
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
作用同epoll_wait,只是可以设置允许捕捉的信号。
参数
epfd: 和epoll实例关联的fd。
events: struct epoll_event结构体数组,用于接收epoll关联fd的事件信息。
maxevents: events的数组大小。
timeout: 等待超时时间,单位毫秒。等于-1表示一直等待。
sigmask: 信号信息掩码。
示例
代码
服务端
源文件名称:epoll_server.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
static void
epoll_addfd_read(int efd, int fd)
{
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
struct epoll_event ep_event;
//ep_event.events = EPOLLIN;
ep_event.events = EPOLLIN | EPOLLET;
ep_event.data.fd = fd;
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ep_event);
}
static void
epoll_removefd(int efd, int fd)
{
epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
static void
do_read(int efd, int fd)
{
char buf[1024];
ssize_t rlen;
int trycnt = 0;
while(1) {
rlen = read(fd, buf, sizeof(buf));
if (rlen >= 0) {
if (rlen == 0) {
printf("connection already close by peer. fd:%d\n", fd);
epoll_removefd(efd, fd);
break;
}
printf("recv len:%ld. fd:%d\n", rlen, fd);
trycnt = 0;
/*
if (rlen < sizeof(buf)) {
break;
}
*/
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (trycnt >= 5) {
printf("try read again trycnt >= 5 break out, fd:%d\n", fd);
break;
}
printf("need read again. fd:%d, trycnt:%d\n", fd, ++trycnt);
} else {
printf("read failed. errno:%d, errstr:%s, fd:%d\n", errno, strerror(errno), fd);
if (errno == ECONNRESET || errno == ENOTCONN) {
printf("connection already close by peer. fd:%d\n", fd);
epoll_removefd(efd, fd);
}
break;
}
}
}
}
static int
do_process(int efd, int listenfd)
{
struct epoll_event events[8], *event;
int fd;
int num = epoll_wait(efd, events, sizeof(events), 1000);
if (num < 0) {
printf("epoll_wait failed. errstr:%s\n", strerror(errno));
return -1;
} else if (num == 0) {
printf("epoll_wait timeout\n");
return 0;
}
int i;
for (i = 0; i < num; ++i) {
event = &events[i];
fd = event->data.fd;
if (fd == listenfd) {
struct sockaddr_storage caddr;
socklen_t caddrlen = sizeof(caddr);
int fd = accept(listenfd, (struct sockaddr *)&caddr, &caddrlen);
if (fd > 0) {
epoll_addfd_read(efd, fd);
if (caddr.ss_family != AF_INET && caddr.ss_family != AF_INET6) {
printf("Unsupport family:%d\n", caddr.ss_family);
} else {
char caddr_str[128] = {0};
uint16_t port;
if (caddr.ss_family == AF_INET) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&caddr;
inet_ntop(AF_INET, (const void *)&(addr4->sin_addr.s_addr), caddr_str, sizeof(caddr_str));
port = ntohs(addr4->sin_port);
} else {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&caddr;
inet_ntop(AF_INET6, (const void *)&(addr6->sin6_addr), caddr_str, sizeof(caddr_str));
port = ntohs(addr6->sin6_port);
}
printf("A new connection(%d) from %s#%u\n", fd, caddr_str, port);
}
} else {
printf("accept call failed. errstr:%s, listenfd:%d\n", strerror(errno), listenfd);
}
} else {
do_read(efd, fd);
}
}
return 0;
}
static int
create_tcp_server(const char *ip, const uint16_t port, const bool ipv6)
{
const struct sockaddr *addr;
socklen_t addrlen;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
int family;
if (ipv6) {
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
if (inet_pton(AF_INET6, ip, &addr6.sin6_addr) == -1) {
printf("inet_pton failed for ipv6. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
return -1;
}
addr = (const struct sockaddr *)&addr6;
addrlen = sizeof(addr6);
family = AF_INET6;
} else {
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr4.sin_addr.s_addr) == -1) {
printf("inet_pton failed for ipv4. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
return -1;
}
addr = (const struct sockaddr *)&addr4;
addrlen = sizeof(addr4);
family = AF_INET;
}
int fd = socket(family, SOCK_STREAM, 0);
if (fd == -1) {
printf("socket failed. errstr:%s\n", strerror(errno));
return -1;
}
int reuse = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
close(fd);
printf("setsockopt for SO_REUSEADDR failed. errstr:%s\n", strerror(errno));
return -1;
}
if (bind(fd, addr, addrlen) == -1) {
close(fd);
printf("bind failed. errstr:%s\n", strerror(errno));
return -1;
}
if (listen(fd, 5) == -1) {
close(fd);
printf("listen failed. errstr:%s\n", strerror(errno));
return -1;
}
return fd;
}
int
main(int argc, char **argv)
{
int svrfd = create_tcp_server("127.0.0.1", 5151, false);
if (svrfd < 0) {
printf("create_tcp_server failed.\n");
return -1;
}
int efd = epoll_create(1024);
if (efd == -1) {
close(svrfd);
printf("epoll_create failed. errstr:%s\n", strerror(errno));
return -1;
}
epoll_addfd_read(efd, svrfd);
int ret;
while (1) {
ret = do_process(efd, svrfd);
if (ret != 0) {
break;
}
}
epoll_removefd(efd, svrfd);
close(efd);
return 0;
}
客户端
源文件名称:epoll_client.c
#include <stdio.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
static void
do_write(int fd, char *data, size_t dlen)
{
ssize_t wlen, awlen = 0;
int trycnt = 0;
while (awlen < dlen) {
wlen = write(fd, (const void *)(data+awlen), dlen-awlen);
if (wlen == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (trycnt >= 5) {
printf("try write again trycnt >= 5 break out. fd:%d\n", fd);
break;
}
printf("need write again. fd:%d, trycnt:%d\n", fd, ++trycnt);
} else {
printf("write failed. errstr:%s, fd:%d\n", strerror(errno), fd);
break;
}
} else {
awlen += wlen;
trycnt = 0;
printf("write succeed. fd:%d, wlen:%ld, awlen:%ld, dlen:%ld\n", fd, wlen, awlen, dlen);
}
}
}
static int
create_tcp_client(const char *ip, const uint16_t port, const bool ipv6)
{
const struct sockaddr *addr;
socklen_t addrlen;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
int family;
if (ipv6) {
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
if (inet_pton(AF_INET6, ip, &addr6.sin6_addr) == -1) {
printf("inet_pton failed for ipv6. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
return -1;
}
addr = (const struct sockaddr *)&addr6;
addrlen = sizeof(addr6);
family = AF_INET6;
} else {
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr4.sin_addr.s_addr) == -1) {
printf("inet_pton failed for ipv4. ip:%s, port:%u, errstr:%s\n", ip, port, strerror(errno));
return -1;
}
addr = (const struct sockaddr *)&addr4;
addrlen = sizeof(addr4);
family = AF_INET;
}
int fd = socket(family, SOCK_STREAM, 0);
if (fd == -1) {
printf("socket failed. errstr:%s\n", strerror(errno));
return -1;
}
if (connect(fd, addr, addrlen) == -1) {
close(fd);
printf("connect failed. errstr:%s\n", strerror(errno));
return -1;
}
return fd;
}
int
main(int argc, char **argv)
{
if (argc <= 1) {
printf("please give send data\n");
return -1;
}
int clifd = create_tcp_client("127.0.0.1", 5151, false);
if (clifd < 0) {
printf("create_tcp_client failed.\n");
return -1;
}
int i;
for (i = 1; i < argc; ++i) {
do_write(clifd, argv[i], strlen(argv[i]));
}
close(clifd);
return 0;
}
执行结果
客户端
xxxx:~$ ./epoll_client adfdf sdfsdfs sss aaaaaa sdf
write succeed. fd:3, wlen:5, awlen:5, dlen:5
write succeed. fd:3, wlen:7, awlen:7, dlen:7
write succeed. fd:3, wlen:3, awlen:3, dlen:3
write succeed. fd:3, wlen:6, awlen:6, dlen:6
write succeed. fd:3, wlen:3, awlen:3, dlen:3
xxxx:~$
服务端
xxxx:~$ ./epoll_server
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
A new connection(5) from 127.0.0.1#55275
recv len:24. fd:5
connection already close by peer. fd:5
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout
epoll_wait timeout