作者: 大呀大帝国
email:[email protected]
EPOLL 的API用来执行类似poll()的任务。能够用于检测在多个文件描述符中任何IO可用的情况。Epoll API可以用于边缘触发(edge-triggered)和水平触发(level-triggered), 同时epoll可以检测更多的文件描述符。以下的系统调用函数提供了创建和管理epoll实例:
边缘触发(edge-triggered 简称ET)和水平触发(level-triggered 简称LT):
epoll的事件派发接口可以运行在两种模式下:边缘触发(edge-triggered)和水平触发(level-triggered),两种模式的区别请看下面,我们先假设下面的情况:
1. 一个代表管道读取的文件描述符已经注册到epoll实例上了。
2. 在管道的写入端写入了2kb的数据。
3. epoll_wait 返回一个可用的rfd文件描述符。
4. 从管道读取了1kb的数据。
5. 调用epoll_wait 完成。
如果rfd被设置了ET,在调用完第五步的epool_wait 后会被挂起,尽管在缓冲区还有可以读取的数据,同时另外一段的管道还在等待发送完毕的反馈。这是因为ET模式下只有文件描述符发生改变的时候,才会派发事件。所以第五步操作,可能会去等待已经存在缓冲区的数据。在上面的例子中,一个事件在第二步被创建,再第三步中被消耗,由于第四步中没有读取完缓冲区,第五步中的epoll_wait可能会一直被阻塞下去。
下面情况下推荐使用ET模式:
相比之下,当我们使用LT的时候(默认),epoll会比poll更简单更快速,而且我们可以使用在任何一个地方。
先简单的看下EPOLL的API
#include
int epoll_create(int size);
int epoll_create1(int flags);
epoll_create() 可以创建一个epoll实例。在linux 内核版本大于2.6.8 后,这个size 参数就被弃用了,但是传入的值必须大于0。
在 epoll_create () 的最初实现版本时, size参数的作用是创建epoll实例时候告诉内核需要使用多少个文件描述符。内核会使用 size 的大小去申请对应的内存(如果在使用的时候超过了给定的size, 内核会申请更多的空间)。现在,这个size参数不再使用了(内核会动态的申请需要的内存)。但要注意的是,这个size必须要大于0,为了兼容旧版的linux 内核的代码。
epoll_create() 会返回新的epoll对象的文件描述符。这个文件描述符用于后续的epoll操作。如果不需要使用这个描述符,请使用close关闭。
epoll_create1() 如果flags的值是0,epoll_create1()等同于epoll_create()除了过时的size被遗弃了。当然flasg可以使用 EPOLL_CLOEXEC,请查看 open() 中的O_CLOEXEC来查看 EPOLL_CLOEXEC有什么用。
返回值: 如果执行成功,返回一个非负数(实际为文件描述符), 如果执行失败,会返回-1,具体原因请查看error.
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
这个系统调用能够控制给定的文件描述符epfd指向的epoll实例,op是添加事件的类型,fd是目标文件描述符。
有效的op值有以下几种:
event这个参数是用于关联制定的fd文件描述符的。它的定义如下:
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这个参数是一个字节的掩码构成的。下面是可以用的事件:
返回值:如果成功,返回0。如果失败,会返回-1, errno将会被设置
有以下几种错误:
#include
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_wait 这个系统调用是用来等待epfd中的事件。events指向调用者可以使用的事件的内存区域。maxevents告知内核有多少个events,必须要大于0.
timeout这个参数是用来制定epoll_wait 会阻塞多少毫秒,会一直阻塞到下面几种情况:
当timeout等于-1的时候这个函数会无限期的阻塞下去,当timeout等于0的时候,就算没有任何事件,也会立刻返回。
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 */
};
每次epoll_wait() 返回的时候,会包含用户在epoll_ctl中设置的events。
还有一个系统调用epoll_pwait ()。epoll_pwait()和epoll_wait ()的关系就像select()和 pselect()的关系。和pselect()一样,epoll_pwait()可以让应用程序安全的等待知道某一个文件描述符就绪或者捕捉到信号。
下面的 epoll_pwait () 调用:
ready = epoll_pwait(epfd, &events, maxevents, timeout, &sigmask);
在内部等同于:
pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = epoll_wait(epfd, &events, maxevents, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
如果 sigmask为NULL, epoll_pwait()等同于epoll_wait()。
返回值:有多少个IO事件已经准备就绪。如果返回0说明没有IO事件就绪,而是timeout超时。遇到错误的时候,会返回-1,并设置 errno。
有以下几种错误:
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',
* (socket(), bind(), listen()) omitted */
epollfd = epoll_create1( 0 );
if ( epollfd == -1 )
{
perror( "epoll_create1" );
exit( EXIT_FAILURE );
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, listen_sock, &ev ) == -1 )
{
perror( "epoll_ctl: listen_sock" );
exit( EXIT_FAILURE );
}
for (;; )
{
nfds = epoll_wait( epollfd, events, MAX_EVENTS, -1 );
if ( nfds == -1 )
{
perror( "epoll_wait" );
exit( EXIT_FAILURE );
}
for ( n = 0; n < nfds; ++n )
{
if ( events[n].data.fd == listen_sock )
{
conn_sock = accept( listen_sock,
(struct sockaddr *) &local, &addrlen );
if ( conn_sock == -1 )
{
perror( "accept" );
exit( EXIT_FAILURE );
}
setnonblocking( conn_sock );
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, conn_sock,
&ev ) == -1 )
{
perror( "epoll_ctl: conn_sock" );
exit( EXIT_FAILURE );
}
} else {
do_use_fd( events[n].data.fd );
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENT 20
#define READ_BUF_LEN 256
/**
* 设置 file describe 为非阻塞模式
* @param fd 文件描述
* @return 返回0成功,返回-1失败
*/
static int make_socket_non_blocking (int fd) {
int flags, s;
// 获取当前flag
flags = fcntl(fd, F_GETFL, 0);
if (-1 == flags) {
perror("Get fd status");
return -1;
}
flags |= O_NONBLOCK;
// 设置flag
s = fcntl(fd, F_SETFL, flags);
if (-1 == s) {
perror("Set fd status");
return -1;
}
return 0;
}
int main() {
// epoll 实例 file describe
int epfd = 0;
int listenfd = 0;
int result = 0;
struct epoll_event ev, event[MAX_EVENT];
// 绑定的地址
const char * const local_addr = "192.168.0.45";
struct sockaddr_in server_addr = { 0 };
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listenfd) {
perror("Open listen socket");
return -1;
}
/* Enable address reuse */
int on = 1;
// 打开 socket 端口复用, 防止测试的时候出现 Address already in use
result = setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
if (-1 == result) {
perror ("Set socket");
return 0;
}
server_addr.sin_family = AF_INET;
inet_aton (local_addr, &(server_addr.sin_addr));
server_addr.sin_port = htons(8080);
result = bind(listenfd, (const struct sockaddr *)&server_addr, sizeof (server_addr));
if (-1 == result) {
perror("Bind port");
return 0;
}
result = make_socket_non_blocking(listenfd);
if (-1 == result) {
return 0;
}
result = listen(listenfd, 200);
if (-1 == result) {
perror("Start listen");
return 0;
}
// 创建epoll实例
epfd = epoll_create1(0);
if (1 == epfd) {
perror("Create epoll instance");
return 0;
}
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET /* 边缘触发选项。 */;
// 设置epoll的事件
result = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
if(-1 == result) {
perror("Set epoll_ctl");
return 0;
}
for ( ; ; ) {
int wait_count;
// 等待事件
wait_count = epoll_wait(epfd, event, MAX_EVENT, -1);
for (int i = 0 ; i < wait_count; i++) {
uint32_t events = event[i].events;
// IP地址缓存
char host_buf[NI_MAXHOST];
// PORT缓存
char port_buf[NI_MAXSERV];
int __result;
// 判断epoll是否发生错误
if ( events & EPOLLERR || events & EPOLLHUP || (! events & EPOLLIN)) {
printf("Epoll has error\n");
close (event[i].data.fd);
continue;
} else if (listenfd == event[i].data.fd) {
// listen的 file describe 事件触发, accpet事件
for ( ; ; ) { // 由于采用了边缘触发模式,这里需要使用循环
struct sockaddr in_addr = { 0 };
socklen_t in_addr_len = sizeof (in_addr);
int accp_fd = accept(listenfd, &in_addr, &in_addr_len);
if (-1 == accp_fd) {
perror("Accept");
break;
}
__result = getnameinfo(&in_addr, sizeof (in_addr),
host_buf, sizeof (host_buf) / sizeof (host_buf[0]),
port_buf, sizeof (port_buf) / sizeof (port_buf[0]),
NI_NUMERICHOST | NI_NUMERICSERV);
if (! __result) {
printf("New connection: host = %s, port = %s\n", host_buf, port_buf);
}
__result = make_socket_non_blocking(accp_fd);
if (-1 == __result) {
return 0;
}
ev.data.fd = accp_fd;
ev.events = EPOLLIN | EPOLLET;
// 为新accept的 file describe 设置epoll事件
__result = epoll_ctl(epfd, EPOLL_CTL_ADD, accp_fd, &ev);
if (-1 == __result) {
perror("epoll_ctl");
return 0;
}
}
continue;
} else {
// 其余事件为 file describe 可以读取
int done = 0;
// 因为采用边缘触发,所以这里需要使用循环。如果不使用循环,程序并不能完全读取到缓存区里面的数据。
for ( ; ;) {
ssize_t result_len = 0;
char buf[READ_BUF_LEN] = { 0 };
result_len = read(event[i].data.fd, buf, sizeof (buf) / sizeof (buf[0]));
if (-1 == result_len) {
if (EAGAIN != errno) {
perror ("Read data");
done = 1;
}
break;
} else if (! result_len) {
done = 1;
break;
}
write(STDOUT_FILENO, buf, result_len);
}
if (done) {
printf("Closed connection\n");
close (event[i].data.fd);
}
}
}
}
close (epfd);
return 0;
}