服务器epoll初用

来源:互联网 发布:网络彩票销售好做吗 编辑:程序博客网 时间:2024/06/05 03:55

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

首先通过epoll_create(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
1
nfds=epoll_wait(kdpfd,events,maxevents,-1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。maxevents是当前需要监听的所有socket句柄数。最后一个timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件发生,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
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结构如下:
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队列里

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/epoll.h>#define MAXLINE 80#define SERV_PORT 8000int main(void){struct sockaddr_in servaddr,cliaddr;struct epoll_event ev,event[20];//定义epoll事件结构体socklen_t cliaddr_len;int listenfd,connfd,epollfd,nfds,newfd;char buf[MAXLINE];char str[INET_ADDRSTRLEN];int i,n,on=1,j;listenfd=socket(AF_INET,SOCK_STREAM,0);epollfd=epoll_create(1024);//创建一个epoll句柄if(epollfd==-1){printf("epoll_create error...\n");exit(0);}ev.data.fd=listenfd;ev.events=EPOLLIN;//对应文件描述符可以读if(epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev)==-1){printf("epoll_ctl error...\n");exit(0);}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_addr.s_addr=htonl(INADDR_ANY);servaddr.sin_port=htons(SERV_PORT);setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0){printf("bind error...\n");exit(0);}listen(listenfd,20);printf("accept connections...\n");while(1){nfds=epoll_wait(epollfd,event,20,-1);printf("nfds=%d\n",nfds);if(nfds==-1){printf("epoll_wait failed...\n");exit(0);}for(j=0;j<nfds;j++){newfd=event[j].data.fd;if(newfd==listenfd){//如果是主socket的事件的话,则表示有新连接进入了,进行新连接的处理。cliaddr_len=sizeof(cliaddr);connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);printf("received from %s at PORT %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));ev.data.fd=connfd;epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&ev);//设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,这里用EPOLL_CTL_ADD来加一个新的epoll事件,通过EPOLL_CTL_DEL来减少一个过EPOLL_CTL_MOD来改变一个事件的监听方式。}else{n=read(newfd,buf,MAXLINE);if(n<=0){close(newfd);}for(i=0;i<n;i++)buf[i]=toupper(buf[i]);write(connfd,buf,n);}}}close(epollfd);//关闭epollfdreturn 0;}



0 0