epoll服务器
来源:互联网 发布:贵州大数据培训 编辑:程序博客网 时间:2024/05/21 13:13
相比select、poll,epoll是I/O多路转接最高效的手段,它几乎具备了之前select、poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。epoll实现只有epoll_create()、epoll_ctl()、epoll_wait()三个系统调用函数。
1、epoll_create()函数:
int epoll_create(int size);
- 含义:创建一个epoll句柄,占用一个fd,使用完成epoll需要关闭此fd;
- 参数:size:从linux2.6.8之后,size参数是被忽略的;
- 返回值:返回一个epfd.
2.epoll_ctl()函数:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 含义:事件注册函数,向epoll模型中增加、修改、删除需要监听的fd,并指定对应关心的事件;
- 参数:
- epfd:epoll_create返回值;
- op:表示动作,用三个宏来表示:(1)EPOLL_CTL_ADD:添加新的fd到epfd中;(2)EPOLL_CTL_MOD:修改以及注册fd监听事件;(3)EPOLL_CTL_DEL:从epfd中删除一个fd;
- fd:需要监听的fd;
- event:需要监听的事件:struct epoll_event结构如下:
struct epoll_event{ _unint32_t events;//关心的事件 epoll_data_t data;};typedef union epoll_data{ void* ptr; int fd; _uint32_t u32; _uint64_t u64;}epoll_data_t;
- events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个
socket的话,需要再次把这个socket加入到EPOLL队列里;
3.epoll_wait()函数:
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 含义:等待epoll模型中哪些fd的事件就绪;
- 参数:
- epfd:epoll句柄;
- events:结构体数组,输出型参数,epoll将会把发生的事件赋值到events数组中;
- maxevents:告之内核这个events数组有多大,这个 maxevents的值不能大于创建epoll_create()时的size值;
- timeout:超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞);
- 返回值:函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
以上即为epoll最基本的操作,其中其最基本的原理为:
- epoll_create()在底层创建一个红黑树与就绪队列,返回一个epoll模型;
- epoll_ctl()即在红黑树插入新的结点,即关心的fd,当其事件就绪,采用回调机制激活;
- epll_wait()只关心就绪队列中的就绪fd,直接对对应fd进行相应事件处理;
- 当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,只需要去epoll指定的数组中依次取得相应数量的文件描述符即可,其使用了内存映射(mmap)技术,彻底省掉了这些文件描述符在系统调用时复制的开销。
- 于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
- Epoll的2种工作方式-水平触发(LT)和边缘触发(ET):LT:是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知,所以,这种模式编程出错误可能性要小一点,传统的select/poll都是这种模型的代表. ET:是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。
epoll模型的优点高效性体现:
- select维护数组,epoll维护红黑树,其增删查改效率较高;
- select遍历数组,epoll只遍历就绪队列,时间复杂度为o(1),并且队列只存储就绪结点;
- epoll监视的fd数目无上限,由于其由红黑树描述的,可以一直创建;
- 其用户与内核采用内存映射机制;
- 就绪队列从0开始连续放置就绪的fd;
- IO效率不随FD数目增加而线性下降;
- 内核微调.
以下实现LT模式下的epoll服务器:
#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <sys/epoll.h>#include <sys/types.h>#include <string.h>#include <stdlib.h>#include <arpa/inet.h>static void usage(const char* proc){ printf("%s [local_ip] [local_port]\n",proc);}int startup(const char* ip,int port){ int sock=socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); exit(3); } int opt=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(port); local.sin_addr.s_addr=inet_addr(ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); close(sock); exit(4); } if(listen(sock,10)<0) { perror("listen"); close(sock); exit(5); } return sock;}int main(int argc,char* argv[]){ if(argc != 3) { usage(argv[0]); return 1; } int listen_sock=startup(argv[1],atoi(argv[2])); //创建监听套接字 int efds=epoll_create(1024); //创建epoll模型 struct epoll_event ev; ev.events=EPOLLIN; ev.data.fd=listen_sock; epoll_ctl(efds,EPOLL_CTL_ADD,listen_sock,&ev); //将监听套接字加入epoll模型中,并关心其读事件,采用激活回调机制 struct epoll_event fds[1024]; //定义共享内存就绪队列大小 int maxnum=1024; //定义最多可以监听多少个事件 int timeout=500; while(1) { int nums=epoll_wait(efds,fds,maxnum,timeout); //一段时间内等待一组文件描述符的事件(实际则直接遍历就绪队列) switch(nums) { case -1: perror("epoll_wait"); break; case 0: printf("timeout\n"); break; default: { int i=0; for( ;i<nums;++i) { int sock=fds[i].data.fd; if(sock==listen_sock && fds[i].events&EPOLLIN) //监听套接字就绪 { struct sockaddr_in client; socklen_t len=sizeof(client); int new_sock=accept(sock,(struct sockaddr*)&client,&len); //建立连接 if(new_sock<0) { perror("accept"); continue; } ev.events=EPOLLIN; ev.data.fd=new_sock; epoll_ctl(efds,EPOLL_CTL_ADD,new_sock,&ev); //将新套接字加入efds模型关心其读事件 } else if(fds[i].events & EPOLLIN) //nomal sock ready //读事件就绪 { char buf[1024]; ssize_t s=read(sock,buf,sizeof(buf)-1); if(s>0) { buf[s]=0; printf("client# %s\n"); ev.events=EPOLLOUT; ev.data.fd=sock; epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev); //读成功关心其写事件 } else if(s==0) { printf("client quit!!!\n"); close(sock); epoll_ctl(efds,EPOLL_CTL_DEL,sock,NULL); } else { perror("read"); continue; } } else if(fds[i].events & EPOLLOUT) //写事件就绪 { char* msg="I am server!!!\n"; ssize_t s=write(sock,msg,strlen(msg)); if(s<0) { perror("write"); continue; } ev.events=EPOLLIN; ev.data.fd=sock; epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev); //写成功关心其读事件 } } } break; } } close(listen_sock); return 0;}
阅读全文
3 1
- epoll服务器
- epoll服务器
- epoll服务器
- epoll服务器
- Epoll服务器
- epoll服务器
- epoll服务器
- linux服务器编程--EPOLL
- epoll服务器示例
- 并发服务器之epoll
- epoll服务器编程-demo
- epoll服务器开发详解
- epoll回显服务器
- Epoll服务器架构
- 服务器epoll初用
- linux下epoll服务器
- python epoll开发服务器
- Epoll模型服务器实现
- 使用apache的poi实现导入导出excel
- ZigBee 3.0 《Base-Device-Behavior-Specification》-- Reset
- Mongodb3.2.9创建管理员帐号与普通帐号
- CodeVS.1380 没有上司的舞会 (树形DP)
- HDU-2017 多校训练赛8-1011-Killer Names
- epoll服务器
- C#使用Xamarin开发可移植移动应用(3.Xamarin.Views控件)附源码
- 常用数字电路逻辑符号
- A hard puzzle
- Oracle和MySQL在SQL语句方面的区别
- [翻译]多种DLL注入技术原理介绍
- WebStart JNLP参数传递
- 安装kvm创建虚拟机的问题:VNC server running on '::1:5900'
- 处理编译错误"0" is an invalid value for the "DebugInformation" parameter of the "DCC"