epoll多路复用

epoll多路复用            
select效率随着描述符的增多,时间增多,因为内部是for循环。而epoll时间是固定的,不随着描述符的增多而增长。
在工作中不再使用select,而是用epoll.

epoll共有三个函数: 
a int  epoll_create(int size);          // 创建一个epoll句柄。现在epoll不限制数量,所以size现在失效了,写1就可以。
b int  epoll_ctl(int epfd, int  op, int fd, struct epoll_event * event);     // 事件注册函数.dpfd是新创建的epoll句柄. op为EPOLL_CTL_ADD,表示                                                                                                      新的fd;为EPOLL_CTL_MOD,表示修改已经注册的fd的监听事件;                                                                                                       EPOLL_CTL_DEL,表示从epfd中删除一个fd。fd是需要监听的fd。event                                                                                                      告诉内核需要监听 什么事。  
typedef union epoll_data{
  void *ptr;
  int fd;
  __uint32_t u32;
  __uint64_t u64;
}epoll_data_t;

 struct  epoll_event{
  __uint32_t events;           // events可以是几个宏的集合。常用的有: EPOLLIN,表示对应的文件描述符可以读(包括对端SOCKET正常
                                               关闭)。 EPOLLET ,表示将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)                                                来说的。
  epoll_data_t data;            
   };

c int  epoll_wait(int epfd, struct epoll_event *  events, int maxevents, int timeout);   //等待事件的产生,类似于select()调用。 epfd为要监听的句                                                                                     柄.从内核得到事件的集合保存到events中。 maxevents告之内核这个events有多大                                                                                       (即,数组成员的个数). timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说                                                                                    是永久阻塞) . 触发了几 个,返回值就是几。
例:用epoll实现即时聊天:    
//通过epoll来实现tcp即时通信:服务端   
#define NUM 10
int main(int argc,char* argv[]){
        if(argc!=3){
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);  
        if(-1==sfd){
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret){
                perror("bind");
                return -1;
        }
        ret=listen(sfd,NUM);
        if(-1==ret){
                perror("listen");
                return -1;
        }
        int epfd=epoll_create(1);                         //创建epoll句柄   
        struct epoll_event event,evs[NUM+1];   //event保存注册的事件的信息,evs                                                                           用来保存epoll_wait监听到了的事件
        event.events=EPOLLIN;
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);     //在epfd中注册sfd  
        if(-1==ret){
                perror("epoll_ctl");
                return -1;
        }
        event.events=EPOLLIN;
        event.data.fd=0;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);        //注册标准输入  
        if(-1==ret){
                perror("epoll_ctl");
                return -1;
        }
        int i;
        int new_fd;
        char buf[128];
        int n;
        while(1){
                memset(evs,0,sizeof(evs));                 //每次都要清空,以便wait重新填写 
                ret=epoll_wait(epfd,evs,NUM+1,-1);//监听epfd,监听的结果放到evs数组                                                        中,数组的元素个数为NUM+1,-1表示一直监听.
                if(ret >0){
                        for(i=0;i<ret;i++){  //返回到了数组中,所以循环从evs重查看  
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd){//新请求
                                        new_fd=accept(sfd,NULL,NULL);
                                        printf("new_fd=%d\n",new_fd);
                                        event.events=EPOLLIN;
                                        event.data.fd=new_fd;
                                        epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event);//注册
                                }
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0){   //输入
                                        memset(buf,0,sizeof(buf));
                                        n=read(0,buf,sizeof(buf));
                                        if(n>0){
                                                send(new_fd,buf,strlen(buf)-1,0);
                                        }else if(n==0){   //在新的一行输入ctrl+d,表示EOF  
                                                printf("bye\n");
                                                event.events=EPOLLIN;
                                                event.data.fd=new_fd;
                                                epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
                                                close(new_fd);      
                                        }
                                }
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd){//对                                                                                                   方向你发送消息      
                                        memset(buf,0,sizeof(buf));
                                        n=recv(new_fd,buf,sizeof(buf),0);   
                                        if(n>0){
                                                printf("%s\n",buf);
                                        }else if(n==0){                        //对方关闭连接了  
                                                printf("bye\n");
                                                event.events=EPOLLIN;
                                                event.data.fd=new_fd;
                                                epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
                                                close(new_fd);
                                                break;
                                        }
                                }      
                        }
                }
        }
        return 0;
}

//客户端   
int main(int argc,char** argv){
        if(argc !=3){
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd){
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret){
                perror("connect");
                return -1;
        }
        int epfd=epoll_create(1);            //创建epoll句柄 
        struct epoll_event event,evs[2];  //只监听2个
        event.events=EPOLLIN;
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd
        if(-1==ret){
                perror("epoll_ctl");
                return -1;
        }
        event.events=EPOLLIN;
        event.data.fd=0;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入 
        if(-1==ret){
                perror("epoll_ctl");
                return -1;
        }
        int i;
        char buf[128];
        int n;
        while(1){
                memset(evs,0,sizeof(evs));
                ret=epoll_wait(epfd,evs,2,-1); 
                if(ret>0){
                        for(i=0;i<ret;i++){
                                if(evs[i].events == EPOLLIN && evs[i].data.fd == 0){
                                        memset(buf,0,sizeof(buf));
                                        n=read(0,buf,sizeof(buf));                              
                                        if(n==0){                   //ctrl+d 结束 
                                                printf("bye\n");
                                                close(sfd);
                                                return 0;
                                        }
                                        n=send(sfd,buf,strlen(buf)-1,0);
                                        if(-1==n){
                                                perror("send");
                                                return -1;
                                        }
                                }
                                if(evs[i].events == EPOLLIN && evs[i].data.fd == sfd){
                                        memset(buf,0,sizeof(buf));
                                        n=recv(sfd,buf,sizeof(buf),0);                                    
                                        if(n > 0){
                                                printf("%s\n",buf);
                                        }else if(n==0){
                                                printf("bye\n");
                                                close(sfd);
                                                return 0;
                                        }
                                }
                        }
                }
        }
        return 0;
}

















注: 在上面的程序中,buf一次能够读128个字符,但是如果对方一次给我传输256个字符,我第一次只能接收128个,然后输出。等到下一次while循环时,epoll_wait会继续触发,我再接收剩下的128个字符。这样不仅我每次只能接收一部分,要分成很多段来显示。而且给epoll_wait增加了压力。
        我们希望,1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.我们想一次把缓冲区里面的数据一次读完。

EPOLL 事件有两种模型:     
Edge Triggered (ET) 边缘触发:只有数据到来,才触发,不管缓存区中是否还有数据。 
Level Triggered (LT) 水平触发:只要有数据都会触发。

水平触发:当缓冲区有数据时,epoll_wait会不断得到触发。
如果发的长度大于buf的长度,会分成多段来打。。。

新的场景:
1.针对客户端的每一次发送的数据,epoll_wait只触发一次。
2.我们想一次把缓冲区里面的数据一次读完。
如果数据来了,我buf比较小,需要读多次。面临两个问题,1.

边缘触发:只有在上升沿的时候才触发。
改成之后:若客户端发送I come from china .则只触发一次。比如buf为10,则只发送I come fro
发送几次数据,我就响应几次。

例:将描述符的属性改为非阻塞    
void change_noblock(int fd){
        int status;
        status= fcntl(fd,F_GETFL);                 //fcntl函数可以改变已打开的文件描述符性质。F_GETFL表示读取文件描述符状态。
        status=status |O_NONBLOCK;            //将状态改为非阻塞。 要想再变回来,与上按位取反。
        int ret= fcntl(fd,F_SETFL,status);        //设置文件描述符状态,status为新状态。
        if(-1==ret){
                perror("fcntl");
                return;
        }
}

int main(){
        char buf[128]={0};
         change_noblock(0);                     //调用函数,将标准输入改为非阻塞,0为标准输入  
        int ret=read(0,buf,sizeof(buf));     //默认情况下是阻塞的,当执行到这里的时候,程序会卡住,等待键盘输入。如果输入字符,ret为                                                            成功读取的字节数,errno=0。如果输入ctrl+d,则ret=0,errno=0。按ctrl+d并不能让read退出,需要自                                                         己根据返回值,写判断语句让read退出。
                                                         //标准输入改为非阻塞之后,如果在read之前输入数据,read会正常读入。如果没有输入数据,read                                                         不会卡着等待输入,而是继续运行,此时ret=-1,errno=11.
        printf("ret=%d\n",ret);
        printf("errno=%d\n",errno);
        printf("buf=%s\n",buf);
        return 0;
}

read(0)函数返回值(标准输入阻塞的情况):
1. >0 成功读取的字节数,errno=0。
2. =0 到达文件尾。读标准输入时,按crtl +d,相当于EOF信号。此时errno=0。
3. -1  发生错误,通过errno确定具体错误值。
read(0)标准输入非阻塞的情况:
1. >0 成功读取的字节数
2. =0 到达文件尾。在阻塞前输入ctrl+d,相当于EOF。此时errno=0.
3. -1 有可能发生错误,通过errno确定具体错误值。 有可能因为没有输入数据,read没有阻塞的返回值,此时返回值是-1,errno=11.     

recv(sfd)的返回值(网络描述符阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 出错
对方没有发送数据,有没有关闭:则读端阻塞。
recv(sfd)的返回值(网络描述符非阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 有可能是出错,通过errno确定具体错误值。 有可能因为读取对应描述符,缓冲区为空,recv不阻塞,返回值是-1,errno=11(EAGAIN)  

例子: 用epoll实现即时聊天且:1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.一次把缓冲区里面的数据一次读完。
//通过epoll来实现tcp即时通信:服务端 
#define NUM 10
void change_noblock(int fd) {            //将文件描述符的属性改为非阻塞  
        int status;
        status=fcntl(fd,F_GETFL);
        status=status|O_NONBLOCK;
        int ret=fcntl(fd,F_SETFL,status);
        if(-1==ret) {
                perror("fcntl");
                return;
        }
}
int main(int argc,char* argv[]) {
        if(argc!=3) {
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd) {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret) {
                perror("bind");
                return -1;
        }
        ret=listen(sfd,NUM);
        if(-1==ret) {
                perror("listen");
                return -1;
        }
        int epfd=epoll_create(1);                                  //创建epoll句柄  
        struct epoll_event event,evs[NUM+1];
        event.events=EPOLLIN;                               
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);      //在epfd中注册sfd   
        if(-1==ret) {
                perror("epoll_ctl");
                return -1;
        }
        event.events=EPOLLIN;                                              
        event.data.fd=0;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);        //注册标准输入 
        if(-1==ret) {
                perror("epoll_ctl");
                return -1;
        }
        int i;
        int new_fd;
        char buf[10];
        int n;
        while(1) {
                memset(evs,0,sizeof(evs));
                ret=epoll_wait(epfd,evs,NUM+1,-1);             //监听epfd 
                if(ret >0) {
                        for(i=0;i<ret;i++) {
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd) //新请求
                                        new_fd=accept(sfd,NULL,NULL);
                                        printf("new_fd=%d\n",new_fd);
                                        event.events=EPOLLIN|EPOLLET;//边沿触发的是否可读
                                        change_noblock(new_fd);     //改变描述符的属性为非阻塞
                                        event.data.fd=new_fd;
                                        epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event); //注册 
                                }
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0) //输入
                                        memset(buf,0,sizeof(buf));
                                        n=read(0,buf,sizeof(buf));
                                        if(n>0) {
                                                send(new_fd,buf,strlen(buf)-1,0);
                                        }else if(n==0) {   //在新的一行输入ctrl+d,表示EOF 
                                                printf("bye\n");
                                                event.events=EPOLLIN;
                                                event.data.fd=new_fd;
                                                epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
                                                close(new_fd);      
                                        }
                                }
                                if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd) {
                                        while(1) {            //通过循环直到把数据读空 
                                                memset(buf,0,sizeof(buf));
                                                n=recv(new_fd,buf,sizeof(buf),0);
                                                if(n>0) {
                                                        printf("%s",buf);
                                                }else if(n == -1 && errno == EAGAIN) {  //读空了 
                                                        break;
                                                }else if(n==0){              //对方关闭连接了
                                                        printf("bye\n");
                                                        event.events=EPOLLIN;
                                                        event.data.fd=new_fd;
                                                        epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
                                                        close(new_fd);
                                                        break;
                                                }
                                        }
                                        printf("\n");         //读完之后打印换行 
                                }      
                        }
                }
        }
        return 0;
}
//通过epoll来实现tcp即时通信:客户端 (未改进) 
int main(int argc,char** argv) {
        if(argc !=3) {
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd) {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));//一定要用htons
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret) {
                perror("connect");
                return -1;
        }
        int epfd=epoll_create(1);
        struct epoll_event event,evs[2];
        event.events=EPOLLIN;
        event.data.fd=sfd;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
        if(-1==ret) {
                perror("epoll_ctl");
                return -1;
        }
        event.events=EPOLLIN;
        event.data.fd=0;
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);
        if(-1==ret) {
                perror("epoll_ctl");
                return -1;
        }
        int i;
        char buf[128];
        int n;
        while(1) {
                memset(evs,0,sizeof(evs));
                ret=epoll_wait(epfd,evs,2,-1);
                if(ret>0) {
                        for(i=0;i<ret;i++) {
                                if(evs[i].events == EPOLLIN && evs[i].data.fd == 0) {
                                        memset(buf,0,sizeof(buf));
                                        n=read(0,buf,sizeof(buf));                              
                                        if(n==0) {
                                                printf("bye\n");
                                                close(sfd);
                                                return 0;
                                        }
                                        n=send(sfd,buf,strlen(buf)-1,0);
                                        if(-1==n) {
                                                perror("send");
                                                return -1;
                                        }
                                }
                                if(evs[i].events== EPOLLIN && evs[i].data.fd ==sfd) {
                                        memset(buf,0,sizeof(buf));
                                        n=recv(sfd,buf,sizeof(buf),0);                                 
                                        if(n > 0) {
                                                printf("%s\n",buf);
                                        }else if(n==0) {
                                                printf("bye\n");
                                                close(sfd);
                                                return 0;
                                        }
                                }
                        }
                }
        }
        return 0;
}


































猜你喜欢

转载自blog.csdn.net/pengchengliu/article/details/80524928