linux epoll用法
epoll 是 linux 特有的 I/O 复用函数。它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集。但是, epoll 需要一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符由 epoll_create 函数来创建:
1 2 3 | #include <sys/epoll.h> int epoll_create( int size); |
size 参数现在是被忽略的,但是,为了兼容性,需要传入一个大于0的数。
epoll_ctl 函数来操作epoll的内核事件表:
1 | int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event); |
epfd是epoll_create返回的文件描述符,op指定操作类型,有如下三种:
1 2 3 4 | /* 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 结构体指针,定义如下:
1 2 3 4 5 6 7 8 9 10 11 | 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 文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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函数,它在一段超时时间内等待一组文件描述符上的事件,定义:
1 2 | 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 事件。这样就不会用多并发的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | #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\n" , errno , strerror ( errno )); exit (1); } int reuseaddr = 1; if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof (reuseaddr))) { fprintf (stderr, "setsockopt: %d, %s\n" , 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\n" , errno , strerror ( errno )); exit (1); } if (-1 == listen(listenfd, 5)) { fprintf (stderr, "listen: %d, %s\n" , 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\n" , 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\n" , 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\n" , 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\n" , 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; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 上周热点回顾(1.20-1.26)
· 【译】.NET 升级助手现在支持升级到集中式包管理