EPOLL纸上谈兵
来源:互联网 发布:如何看windows版本 编辑:程序博客网 时间:2024/05/14 09:37
之所以是纸上谈兵,是因为在工作中写的服务器架构中,还是select/poll,没有尝试过epoll,下一次尝试一下。
比较好的几个讨论:
论epoll的使用:http://www.cppblog.com/peakflys/archive/2012/08/26/188344.html
浅谈服务器单I/O线程+工作者线程池模型架构及实现要点: http://www.cnblogs.com/ccdev/p/3542669.html
select、poll、epoll之间的区别总结: http://www.cnblogs.com/Anker/p/3265058.html
三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,
第四个参数是告诉内核需要监听什么事,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 */ };
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);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents最多监听事件的数量,maxevents的值不能大于创建epoll_create(int size)时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
两种模型:
Edge Triggered (ET)
只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件,则只是汇报一次。必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
i 基于非阻塞文件句柄
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。
但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
Level Triggered (LT)
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如广域网环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。
使用总结
1.EPOLL 默认是水平触发模式LT, 如果要使用边缘触发模式ET,要将listen的socket事件加上EPOLLET
ev.data.fd = listen_fd;ev.events = EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLERR | EPOLLET;
//add server socket fd in to event set
epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev );
2.需要设置非阻塞
int opts; opts = fcntl( sock_fd, F_GETFL ); if( opts < 0 ) { printf("err:fcntl F_GETFL"); exit( 1 ); } opts = opts | O_NONBLOCK ; if( fcntl( sock_fd, F_SETFL, opts ) < 0 ) { printf("err:fcntl F_SETFL"); exit( 1 ); }
3.如果是以LT方式处理,只要数据未处理完(send、recv内核缓冲有数据)就会不停的收到 EPOLLIN/EPOLLOU消息(已设置监听)。如果是以ET方式处理,事件只会通知一次,如数据到达可接收,需要使用while循环来接入数据。同样可发送也只会通常一次。send/recv都以收到EAGAIN为标识,如果还没处理完,才会现收到事件通知。
注意EPOLLOUT事件,如果是LT模式,只要send缓冲区空闲就会一直有这个事件通知,程序需要判断是否有数据可发送。如果是ET模式,只有在accept一个socket时触发一次,除非发送,缓冲区满了,再变为空闲时,才会再通知一次
4 ET模式EPOLLOUT和EPOLLIN触发时刻
ET模式称为边缘触发模式,顾名思义,不到边缘情况,是死都不会触发的。
EPOLLOUT事件:EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那你要先准备好下面条件:
1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。
2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。
简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!
其实,如果你真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。
EPOLLIN事件:EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。
其余要点:
1、如果fd被注册到两个epoll中时,如果fd有事件发生则两个epoll都会触发事件。
2、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。
3、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。
4、epoll_wait会一直监听EPOLLHUP事件发生,所以其不需要添加到events中。
5、为了避免大数据量io时,ET模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在循环检测所有事件后轮询ready fd列表。
示例:
Server
#include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> #include <pthread.h> #include <errno.h> #define MAX_LINE_LEN 1024 #define OPEN_MAX 256 #define SVR_PORT 17143 struct STask { int fd; struct STask * next; }; struct SData { int fd; int size; char data[ MAX_LINE_LEN ];}; int epfd;struct epoll_event ev, events[20]; void setNonBlock( int sock_fd ) { int opts; opts = fcntl( sock_fd, F_GETFL ); if( opts < 0 ) { printf("err:fcntl F_GETFL"); exit( 1 ); } opts = opts | O_NONBLOCK ; if( fcntl( sock_fd, F_SETFL, opts ) < 0 ) { printf("err:fcntl F_SETFL"); exit( 1 ); }}bool setSockReuse( int sock_fd ) { int flags = 1; if(setsockopt( sock_fd , SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) == -1) { return false; } printf("set sock reuse ok.\n"); return true;} void OnAccept( int conn_fd, sockaddr_in * cln_addr ) { setNonBlock( conn_fd ); char * str = inet_ntoa( cln_addr->sin_addr ); printf("connect_from:%s\n", str); //add to epoll event for reading event ev.data.fd = conn_fd; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; epoll_ctl( epfd, EPOLL_CTL_ADD, conn_fd, &ev ); } void OnRead( epoll_event * aEv ) { int n; struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) ); newData->fd = aEv->data.fd; int bRead = 1; int recv_len = 0; memset( newData->data, 0, MAX_LINE_LEN ); while( bRead ) { n = read( aEv->data.fd, newData->data + recv_len, 5 ); printf("read return n:%d\n",n); recv_len += n; if( n < 0 ) { if( errno == ECONNRESET ) //connection reset { close( aEv->data.fd ); aEv->data.fd = -1; break; } else if( errno == EAGAIN ) //read interupt(no data in buf) { printf("EAGAIN\n"); break; } else if( errno == EWOULDBLOCK ) //operation would block { perror("recv not over...\n"); } else { printf("read err"); break; } } else if( n == 0 ) { close( aEv->data.fd ); aEv->data.fd = -1; break; } } newData->data[ recv_len ] = '\n'; printf("read: %s", newData->data ); free( newData ); //trigger EPOLLOUT event ev.data.fd = aEv->data.fd; ev.events = EPOLLIN | EPOLLOUT | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, aEv->data.fd ,&ev);} void OnWrite( epoll_event * aEv ) { struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) ); newData->fd = aEv->data.fd; newData->size = snprintf( newData->data, MAX_LINE_LEN, "hello! fd=%d\n", aEv->data.fd ); if( 0 != newData->size ) { printf("send back: %s\n", newData->data ); write( aEv->data.fd , newData->data, newData->size ); } free( newData );} int main(){ int i; int listen_fd, conn_fd,sock_fd, nfds; epfd = epoll_create( OPEN_MAX ); struct sockaddr_in svrAddr; struct sockaddr_in clnAddr; socklen_t clnLen; listen_fd = socket( AF_INET, SOCK_STREAM, 0); setNonBlock( listen_fd ); setSockReuse( listen_fd ); ev.data.fd = listen_fd; ev.events = EPOLLIN | EPOLLET | EPOLLOUT | EPOLLHUP | EPOLLERR; //add server socket fd in to event set epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev ); memset( &svrAddr, 0 , sizeof( sockaddr_in ) ); inet_aton( "127.0.0.1", &svrAddr.sin_addr ); svrAddr.sin_family = AF_INET; svrAddr.sin_port = htons( SVR_PORT ); int ret; ret = bind( listen_fd, (sockaddr*)&svrAddr, sizeof( sockaddr_in ) ); if( ret != 0 ) { perror( "bind fail" ); exit(-3); } listen( listen_fd, 20 ); for(;;) { nfds = epoll_wait( epfd, events, 20, 500 ); //maybe serval event arrive for(i=0; i<nfds;++i) { if( events[i].data.fd == listen_fd ) { printf("event accept.\n"); clnLen = sizeof( sockaddr_in ); memset( &clnAddr, 0 , sizeof( sockaddr_in ) ); conn_fd = accept( listen_fd, (sockaddr*)&clnAddr, &clnLen ); if( conn_fd < 0 ) { if( errno == EWOULDBLOCK ) printf("EWOULDBLOCK\n"); perror("conn_fd < 0"); break; } OnAccept( conn_fd, &clnAddr ); } else if( events[i].events & EPOLLIN ) //对应的文件描述符可以读(包括对端SOCKET正常关闭) { printf("event EPOLLIN.\n"); if( ( sock_fd = events[i].data.fd ) < 0 ) continue; OnRead( &events[i] ); } else if( events[i].events & EPOLLOUT ) //对应的文件描述符可以写 { printf("event EPOLLOUT.\n"); if( ( sock_fd = events[i].data.fd ) < 0 ) continue; OnWrite( &events[i] ); } else if( events[i].events & EPOLLHUP ) //对应的文件描述符被挂断 { printf("event EPOLLHUP. fd=%d.\n", events[i].data.fd ); } else if( events[i].events & EPOLLERR ) //对应的文件描述符发生错误 { printf("event EPOLLERR. fd=%d.\n", events[i].data.fd ); } } } close(epfd); return 0; }
Clinet
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h> int main(int argc, char *argv[]){if (argc < 3){fprintf(stderr,"Please enter the server's hostname!\n");return -1;}int sockfd;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("socket bind");return -2;}printf("IP: %s, PORT: %d\n", argv[1], atoi(argv[2]));struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1){perror("connect error");return -3;}const char* str="Hello World";if (send(sockfd, str, strlen(str)+1, 0) == -1){perror("send error");}char buf[1024];for(int i = 0; i != 2; i++){int rn = read(sockfd, buf, 1024);buf[rn] = '\0';printf("%d: %s\n", i, buf);send(sockfd, buf, strlen(buf), 0);sleep(1);}close(sockfd);return 0;}
- EPOLL纸上谈兵
- 纸上谈兵
- 纸上谈兵-Dota2
- 纸上谈兵设计模式
- 设计模式不能纸上谈兵
- 纸上谈兵之求职信
- 韩香道有市场并非纸上谈兵
- 纸上谈兵: 图 (graph)
- 纸上谈兵:c++细节
- 纸上谈兵: 图 (graph)
- 纸上谈兵: 哈希表 (hash table)
- 纸上谈兵没有大智慧
- 戏说春秋一纸上谈兵
- 码农看车之纸上谈兵
- epoll
- epoll
- epoll
- epoll
- JavaScript去除字符串首尾空格
- 类成员函数的重载、重写、和覆盖区别
- 运放稳定性连载16:电容性负载稳定性:噪声增益及 CF(2)
- Struts2使用Kindeditor4.0.3在线编辑器--上传图片、视频、FLASH、附件
- linux下svn客户端安装及环境配置
- EPOLL纸上谈兵
- Java中各种修饰符与访问修饰符的说明--基础
- SAP 权限与角色设计
- 快速地将Excel数据导入到SQL2005中的方法
- Java Timer 和 TimerTask 那些事
- JavaScript十六进制字符串和字节数组相互转换
- Android学习笔记(11)---关于布局的一些小事
- c++ 位运算
- Android makefile探索1