linux epoll用法
Posted 人生天地之间,若白驹之过隙,忽然而已
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux epoll用法相关的知识,希望对你有一定的参考价值。
epoll 是 linux 特有的 I/O 复用函数。它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集。但是, epoll 需要一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符由 epoll_create 函数来创建:
#include <sys/epoll.h> int epoll_create(int size);
size 参数现在是被忽略的,但是,为了兼容性,需要传入一个大于0的数。
epoll_ctl 函数来操作epoll的内核事件表:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd是epoll_create返回的文件描述符,op指定操作类型,有如下三种:
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */ #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */ #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */ #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */
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 是成员描述符事件类型。事件类型也定义在 sys/epoll.h 文件中
enum EPOLL_EVENTS { EPOLLIN = 0x001, #define EPOLLIN EPOLLIN EPOLLPRI = 0x002, #define EPOLLPRI EPOLLPRI EPOLLOUT = 0x004, #define EPOLLOUT EPOLLOUT EPOLLRDNORM = 0x040, #define EPOLLRDNORM EPOLLRDNORM EPOLLRDBAND = 0x080, #define EPOLLRDBAND EPOLLRDBAND EPOLLWRNORM = 0x100, #define EPOLLWRNORM EPOLLWRNORM EPOLLWRBAND = 0x200, #define EPOLLWRBAND EPOLLWRBAND EPOLLMSG = 0x400, #define EPOLLMSG EPOLLMSG EPOLLERR = 0x008, #define EPOLLERR EPOLLERR EPOLLHUP = 0x010, #define EPOLLHUP EPOLLHUP EPOLLRDHUP = 0x2000, #define EPOLLRDHUP EPOLLRDHUP EPOLLEXCLUSIVE = 1u << 28, #define EPOLLEXCLUSIVE EPOLLEXCLUSIVE EPOLLWAKEUP = 1u << 29, #define EPOLLWAKEUP EPOLLWAKEUP EPOLLONESHOT = 1u << 30, #define EPOLLONESHOT EPOLLONESHOT EPOLLET = 1u << 31 #define EPOLLET EPOLLET };
data 是 epoll_data_t 联合体类型。可以用fd 表示文件描述符,或者用ptr指针指向更多的用户数据。
epoll 系列系统调用的主要接口是epoll_wait函数,它在一段超时时间内等待一组文件描述符上的事件,定义:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
成功时,返回就绪文件描述符的个数,失败返回-1,并设置errno
其中,timeout指定超时时间,单位毫秒。-1表示永远等待直到有文件描述符就绪。
maxevents 指定最多监听多少个事件,它必须大于0
epoll_wait 函数如果检测到时间,就将事件从内核事件表中复制到第二个参数events指向的数组中。这个数组只输出epoll_wait检测到的就绪事件。
epoll 对文件描述符的操作有两种模式:LT(level trigger 电平触发)和 ET(edge trigger 边沿触发)。LT是默认的工作模式。在这种模式下,文件描述符会一直被检测到直到应用程序处理它。ET模式下,文件描述符就绪,被通知给应用程序,之后,就不再通知该同一事件了。ET模式降低了同一个epoll时间被重复触发的次数,因此效率较高。
EPOLLONESHOT事件
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写、异常事件,且只触发一次,除非我们使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件。这样就不会用多并发的问题。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/epoll.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <fcntl.h> #include <signal.h> #include <map> #include <string> using namespace std; #define CLIENTSIZE 5000 #define BUFSIZE 4000 int createSocket() { struct sockaddr_in servaddr; int listenfd = -1; if (-1 == (listenfd = socket(PF_INET, SOCK_STREAM, 0))) { fprintf(stderr, "socket: %d, %s ", errno, strerror(errno)); exit(1); } int reuseaddr = 1; if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) { fprintf(stderr, "setsockopt: %d, %s ", errno, strerror(errno)); exit(1); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = PF_INET; servaddr.sin_port = htons(8008); inet_pton(PF_INET, "0.0.0.0", &servaddr.sin_addr); if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) { fprintf(stderr, "bind: %d, %s ", errno, strerror(errno)); exit(1); } if (-1 == listen(listenfd, 5)) { fprintf(stderr, "listen: %d, %s ", errno, strerror(errno)); exit(1); } return listenfd; } int setnoblock(int fd) { int oldopt = fcntl(fd, F_GETFL); int newopt = oldopt | O_NONBLOCK; fcntl(fd, F_SETFL, newopt); return oldopt; } void ErrExit(const char* reason) { fprintf(stderr, "%s: %d, %s ", reason, errno, strerror(errno)); exit(1); } void addsig(int sig, void (*handler)(int)) { int olderrno = errno; struct sigaction ac; memset(&ac, 0, sizeof(ac)); ac.sa_handler = handler; ac.sa_flags |= SA_RESTART; sigfillset(&ac.sa_mask); if (-1 == sigaction(sig, &ac, NULL)) { ErrExit("sigaction"); } errno = olderrno; } void addfd(int epfd, int fd) { struct epoll_event ev; ev.events = EPOLLIN | EPOLLET | EPOLLERR; ev.data.fd = fd; if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev)) { ErrExit("epoll_ctl"); } setnoblock(fd); } void delfd(int epfd, int fd) { struct epoll_event ev; ev.data.fd = fd; if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) { ErrExit("epoll_ctl"); } } int main(int argc, char const *argv[]) { int listenfd = createSocket(); int epfd = -1; map<int, string> mapdata; if (-1 == (epfd = epoll_create(CLIENTSIZE))) { ErrExit("epoll_create"); } struct epoll_event evs[CLIENTSIZE]; addfd(epfd, listenfd); while (1) { int connnum = epoll_wait(epfd, evs, CLIENTSIZE, -1); for (int i = 0; i < connnum; ++i) { if (evs[i].events & EPOLLERR) { printf("%d exit ", evs[i].data.fd); delfd(epfd, evs[i].data.fd); close(evs[i].data.fd); mapdata.erase(evs[i].data.fd); } else if (evs[i].data.fd == listenfd && (evs[i].events & EPOLLIN)) { struct sockaddr_in client; socklen_t len = sizeof(client); int cfd = accept(listenfd, (struct sockaddr*)&client, &len); if (cfd == -1) { ErrExit("accept"); } printf("get connection: %d ", cfd); addfd(epfd, cfd); } else if (evs[i].events & EPOLLIN) { char buf[BUFSIZE] = {0}; int len = recv(evs[i].data.fd, buf, BUFSIZE-1, 0); if (len > 0) { mapdata[evs[i].data.fd] = buf; evs[i].events &= (~EPOLLIN); evs[i].events |= EPOLLOUT; if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, evs[i].data.fd, &evs[i])) { ErrExit("epoll_ctl"); } } else if (len == 0) { printf("%d exit ", evs[i].data.fd); delfd(epfd, evs[i].data.fd); close(evs[i].data.fd); mapdata.erase(evs[i].data.fd); } else { ErrExit("recv"); } } else if (evs[i].events & EPOLLOUT) { if (send(evs[i].data.fd, mapdata[evs[i].data.fd].c_str(), mapdata[evs[i].data.fd].size(), 0) < 0) { if (errno == 104) { continue; } ErrExit("send"); } evs[i].events &= (~EPOLLOUT); evs[i].events |= EPOLLIN; if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, evs[i].data.fd, &evs[i])) { ErrExit("epoll_ctl"); } } } } close(listenfd); close(epfd); return 0; }
以上是关于linux epoll用法的主要内容,如果未能解决你的问题,请参考以下文章
linux网络编程 - epoll边沿触发/水平触发内核实现代码分析