epoll是Linux特有的I/O复用函数,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率;并且epoll使用一组函数来完成任务,而不是单个函数,它无须遍历整个被侦听的描述符集,只要遍历那些内核I/O时间异步唤醒而加入ready队列的描述符集合即可。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
1、这个文件描述符使用epoll_create函数来创建:
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。
2、使用epoll_create函数来操作内核事件表
epoll的事件注册函数,它不同与select函数是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
参数:
epfd:要操作的事件表的文件描述符
op:指定要操作的类型
1)EPOLL_CTL_ADD:往事件表中注册fd上的事件
2)EPOLL_CTL_MOD:修改fd上的注册事件
3)EPOLL_CTL_DEL:删除fd上的注册事件
event:指定事件,它是epoll_event结构类型的指针。
其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”;data成员用于存储用户数据。
3、epoll_wait函数:在一段超时时间内等待一组文件描述符上的事件。该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno。
epoll_wait函数如果检测到事件,就将所有就绪事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件。
maxevents:告诉内核这个events参数有多大,这个maxevents的值不能小于创建epoll_create()时的size。 #include<netinet/in.h> #include<arpa/inet.h> #include<sys/epoll.h> #define LISTEN_BACK_LOG 10 //创建 监听套接字 int startup(char* ip,int port) { int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("socket"); exit(1); } int op=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(int)); 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"); exit(2); } if(listen(sock,LISTEN_BACK_LOG)<0) { perror("listen"); exit(3); } return sock; } int main(int argc,char* argv[]) { if(argc !=3) { printf("please enter:%[ip][port]\n",argv[0]); exit(4); } int listen_sock=startup(argv[1],atoi(argv[2])); //创建 epoll 句柄 事件表 int epfd=epoll_create(101);//这里数字不固定, 101只是告诉内核预计用101个文件描述符,但实际这个参数不起作用 if(epfd<0) { perror("epoll_create"); exit(5); } struct epoll_event event; event.events=EPOLLIN;//读 事件 列表 event.data.fd=listen_sock; //向事件表中增加事件 epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&event); struct epoll_event fd_events[100]; int size=sizeof(fd_events)/sizeof(fd_events[0]); int i=0; for(i=0;i<size;i++) { fd_events[i].events=0; fd_events[i].data.fd=-1; } int nums=0; int timeout=10000; int done=0; while(!done) { //返回就绪但文件个数 nums=epoll_wait(epfd,fd_events,size,timeout); switch(nums) { case 0: printf("timeout...\n"); break; case -1: printf("epoll_wait\n"); exit(6); default: { for(i=0;i<nums;i++) { int fd=fd_events[i].data.fd; if((fd==listen_sock)&&(fd_events[i].events & EPOLLIN)) { //listen socket 有新的连接请求 struct sockaddr_in peer; socklen_t len=sizeof(peer); int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len); if(new_sock<0) { perror("accept"); continue; } printf("get a new client,socket->%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port)); event.events=EPOLLIN; event.data.fd=new_sock; //将new_sock 添加进内核事件表 epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&event); } else { //other socket //读事件满足 处理客户端发送的 数据 if(fd_events[i].events & EPOLLIN) { char buf[1024]; memset(buf,'\0',sizeof(buf)); ssize_t _s=recv(fd,buf,sizeof(buf)-1,0); if(_s>0) { printf("client:%s\n",buf); event.events= EPOLLOUT;//将fd事件改写 方便服务器 给请求的客户端发数据 epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&event); } else if(_s==0) { printf("client close...\n"); epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);//空表示不关心返回值 close(fd); } else { perror("recv"); continue; } } else if(fd_events[i].events & EPOLLOUT) { char *msg="HTTP/1.1 200 OK\r\n\r\n<html><h1>hello ^_^<h1></html>\r\n"; send(fd,msg,strlen(msg),0); epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); close(fd); } else { } } } } break; } } exit(0); }