epoll是Linux在2.6内核新增的系统调用,为了更高效地实现多路IO复用。与poll和select相比,它的高效体现在
1、 select和poll都是线性扫描FD(文件描述符)的集合,随着集合的增大,性能自然下降,且不能通过返回值得知那些文件描述符有I/O事件发生,还要再便利一边集合才能找出有I/O事件的文件描述符。而epool只是管理活跃的I/O的FD,不会因为集合增大而性能下降。
2、 select和poll是通过内存拷贝的方式,把FD消息从内核空间拷贝到用户空间,效率地下。epoll是通过共享内存方式。
epool的使用也比较简单。说明如下:
1、创建epoll句柄。
int epoll_create(int size);
参数size是监听句柄的最大数目,它的大小和机器内存有关。返回值是创建的epoll句柄。句柄使用完后调用close()关闭,因为它占用系统的FD。创建epoll句柄的本质是向内核申请空间,用来存放关注的FD集合以及事件。
2、epoll事件注册函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)
注册想要监听的事件类型。参数epfd是epoll_create创建的句柄。
参数op表示动作,在使用时用三个宏来表示:
EPOLL_CTL_ADD表示注册新的fd到epfd中。
EPOLL_CTL_MOD表示修改已经注册过的fd的监听事件。
EPOLL_CTL_DEL表示从epf中删除fd。
参数fd是要监听的fd。
参数event是向内核注册的监听事件。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_data_t data; };
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3、等待事件
int epoll_wait(int epfd,struct epoll_event* events, int maxevents,int timeout)
参数epfd是epoll_create创建的句柄。参数events是从内核得到的事件的集合。参数maxevents告诉内核events的大小,它可以大于epoll_create时指定的size的大小。参数timeout是超时事件,单位为毫秒。
函数返回需要处理的事件的个数,返回0则表示已超时。
epoll事件的触发有两种模式,边沿触发Edge Triggered(ET)和水平Level Triggered(LT)。
ET模式:边沿触发是指当监听的FD状态变换时获得通知。例如接收数据的FD缓存一次接收了2k数据,但是读取时只读取了1k,缓存还有1k数据,但是不会再获得通知。所以采用边沿触发时,如果读取的数据长度等于设定值的话就要继续读取,直到产生EAGAIN。ET模式只支持非阻塞的I/O,以防止一个句柄的读/写操作把处理多个文件描述符的任务饿死。
LT模式:水平触发是默认的模式,它支持阻塞和非阻塞I/O。当I/O就绪时,就会获得通知,如果不对I/O进行处理或没有处理完,内核就会一直通知。
下面结合网上例子,用epoll写一个Echo服务器。实现很简单,把accept、recv、send的I/O操作放到epoll中,有了I/O事件后调用相应的回调函数即可。具体步骤如下:
1、初始化监听FD,把accept事件作为回调函数 ‘AcceptConnection’,等待客户端connect后在服务的调用。
2、在回调函数’ AcceptConnection’中,把accept后的FD注册为等待EPOLLIN,即等待客户端send操作,回调函数为’RecvData‘。
3、客户端写操作(send)后,服务端调用了’RecvData’,在这个函数中,把对应的FD的I/O事件改为EPOLLOUT,回调函数为’SendData’,因为下一步就是向客户端写收到的数据了。
4、在’SendData’函数中,发送接收到的数据,并把对应FD等待事件改为EPOLLIN,重新等待客户端的send操作。
在实现时,用到了封装的数据结构myevent,目的是为了便于操作事件,把事件的回调函数、buffer、上一次激活事件等做了封装。并使用了全局数组集合来作为事件的集合。
代码如下:
#include <iostream>#include <sys/epoll.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <errno.h>#include <string.h> #include <stdlib.h>#define MAX_EVENTS 1000struct myevent{public: int fd; void (*callBack)(int fd, int events, void* arg); int events; void* arg; bool status; char buffer[128]; int len; int offset; long lastActiveTime;};int g_epollFd;myevent g_Events[MAX_EVENTS+1];void SetEvent(myevent* ev, int fd, void(*callBack)(int, int , void*), void* arg){ ev->fd=fd; ev->callBack=callBack; ev->events=0; ev->arg=arg; ev->status=false; bzero(ev->buffer,sizeof ev->buffer); ev->offset=0; ev->len=0; ev->lastActiveTime=time(NULL);}void SetEventCallback(myevent* ev, void(*callBack)(int, int ,void*)){ ev->callBack=callBack;}void AddEvent(int epfd, int events, myevent* ev){ struct epoll_event epv={0,{0}}; epv.data.ptr=ev; epv.events=ev->events=events; int op; if(ev->status) op=EPOLL_CTL_MOD; else { op=EPOLL_CTL_ADD; ev->status=true; } if(epoll_ctl(epfd,op,ev->fd,&epv)<0) printf("Event add failed FD=%d,events=%d\n",ev->fd,events); else printf("Event add successfully,FD=%d,op=%d,events=%0x\n",ev->fd,op,events);}void DelEvent(int epfd, myevent* ev){ struct epoll_event epv={0,{0}}; if(!ev->status) return; epv.data.ptr=ev; ev->status=false; epoll_ctl(epfd,EPOLL_CTL_DEL,ev->fd,&epv);}void RecvData(int fd, int events, void* arg);void SendData(int fd, int events, void* arg);void AcceptConnection(int fd, int events, void* arg){ struct sockaddr_in sin; socklen_t len=sizeof(struct sockaddr_in); int nfd=accept(fd,(struct sockaddr*)&sin,&len) ; if(nfd==-1) { if(errno!= EAGAIN&&errno!=EINTR) {} printf("%s, accept, %d",__func__,errno); } int i; do { for( i=0; i<MAX_EVENTS;++i) { if(g_Events[i].status==false) break; } if(i==MAX_EVENTS) { printf("%s:max connection limit %d\n",__func__,MAX_EVENTS ); break; } int iret=fcntl(nfd,F_SETFL, O_NONBLOCK); if(iret<0) { printf("%s, fctl nonblocking failed:%d",__func__, iret); break; } SetEvent(&g_Events[i], nfd, RecvData,&g_Events[i]); AddEvent(g_epollFd,EPOLLIN,&g_Events[i]); }while(0); printf("new connection[%s:%d] [time:%d], pos[%d]\n",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),g_Events[i].lastActiveTime,i);}void RecvData(int fd, int events, void* arg){ struct myevent* ev=(struct myevent*)arg; int len=recv(fd,ev->buffer+ev->len,sizeof(ev->buffer)-1-ev->len,0); DelEvent(g_epollFd,ev); if(len>0) { ev->len+=len; ev->buffer[len]='\0'; printf("C[%d]:%s\n",fd,ev->buffer); SetEventCallback(ev,SendData); AddEvent(g_epollFd,EPOLLOUT,ev); } else if(len==0) { close(ev->fd); printf("[fd=%d] pos[%d], closed gracefully.\n",fd, ev->events); } else { close(ev->fd); printf("recv[fd=%d] errno[%d]:%s\n",fd,errno,strerror(errno)); }}void SendData(int fd, int events, void* arg){ struct myevent* ev=(struct myevent*)arg; int len=send(fd, ev->buffer+ev->offset, ev->len-ev->offset, 0); if(len>0) { printf("send [fd=%d],[%d<->%d]%s\n",fd,len,ev->len,ev->buffer); ev->offset+=len; if(ev->offset==ev->len) { DelEvent(g_epollFd,ev); SetEvent(ev,fd,RecvData,ev); AddEvent(g_epollFd,EPOLLIN,ev); } } else { close(ev->fd); DelEvent(g_epollFd,ev); printf("send[fd=%d] errno[%d]\n",fd,errno); }}void InitListenSocket(int epfd, short port){ int listenFd=socket(AF_INET, SOCK_STREAM,0); fcntl(listenFd, F_SETFL, O_NONBLOCK); printf("server listen fd=%d\n",listenFd); SetEvent(&g_Events[MAX_EVENTS], listenFd, AcceptConnection, &g_Events[MAX_EVENTS]); AddEvent(epfd, EPOLLIN,&g_Events[MAX_EVENTS]); sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_family=AF_INET; sin.sin_addr.s_addr=INADDR_ANY; sin.sin_port=htons(port); bind(listenFd,(const sockaddr*)&sin, sizeof(sin)); listen(listenFd,MAX_EVENTS);}int main(int argc, char* argv[]){ unsigned short port=12345; if(argc==2) { port=atoi(argv[1]); } g_epollFd=epoll_create(MAX_EVENTS); if(g_Events<=0) printf("Create epoll failed.\n"); InitListenSocket(g_epollFd,port); printf("Server running:port %d\n",port); struct epoll_event events[MAX_EVENTS]; int checkPos=0; while(true) { long now=time(NULL); for(int i=0; i< 100; ++i, checkPos++) { if(checkPos==MAX_EVENTS) checkPos=0; if(g_Events[checkPos].status==false) continue; long duration=now - g_Events[checkPos].lastActiveTime; if(duration>=60) { close(g_Events[checkPos].fd); printf("fd=%d timeout:%d--%d\n",g_Events[checkPos].fd, g_Events[checkPos].lastActiveTime , now); DelEvent(g_epollFd,&g_Events[checkPos]); } } int fds=epoll_wait(g_epollFd,events,MAX_EVENTS,1000); if(fds<0) { printf("epoll_wait error\n"); break; } for(int i=0; i<fds;++i) { myevent* ev=(struct myevent*)events[i].data.ptr; if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) { ev->callBack(ev->fd, events[i].events, ev->arg); } if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) { ev->callBack(ev->fd, events[i].events, ev->arg); } } } return 0;}
- 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
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 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
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
测试客户端如下:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <stdlib.h>#include <errno.h>#include <arpa/inet.h>#include <netinet/in.h>#include <string.h>int main(int argc, char* argv[]){ int fd=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(fd<0) { perror("Create Scoket Error\n"); exit(EXIT_FAILURE); } struct sockaddr_in serverAddr; bzero(&serverAddr, sizeof serverAddr); serverAddr.sin_family=AF_INET; serverAddr.sin_port=htons(12345); serverAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); if(connect(fd, (struct sockaddr*)&serverAddr, sizeof serverAddr)<0) { perror("Connect Error\n"); exit(EXIT_FAILURE); } char sendBuf[128]={0}; char recvBuf[128]={0}; while(fgets(sendBuf, sizeof sendBuf, stdin) != NULL) { write(fd, sendBuf, strlen(sendBuf)); read(fd, recvBuf, sizeof recvBuf); fputs(recvBuf, stdout); bzero(sendBuf, sizeof sendBuf); bzero(recvBuf, sizeof recvBuf); } close(fd); return 0;}