Linux关于IO复用(epoll模型)

在这篇开始之前,可以查看前一篇对poll的概念的描述,这样阅读起这篇比较不困难。首先我们要知道,epoll模型和前面poll,select是有差别的,他实现的方法不大一样,我们来看看下面的代码,为了和之前的poll,select进行区别,我们依旧采用C/S架构实现。服务器代码不同,客户端都是一样的,好的,废话不多说,先上代码,再进行分析。

服务器代码:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/types.h>  
#include <sys/wait.h>
#include <sys/socket.h>  
#include <netinet/in.h> /* for struct sockaddr_in*/  
#include <sys/errno.h>  
#include <signal.h> 
#include <sys/select.h>
#include <sys/epoll.h>



//关于IO复用服务器的epoll

#define LISTEN_SIZE 1024
void error_exit(char *name)
{
   perror(name);
   exit(-1);
}
int main(int argc,char *argv[])
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);
   if(sockfd<0)
   {
        error_exit("create error");
   }
   //绑定地址(ip和端口号)
   struct sockaddr_in svraddr;
   memset(&svraddr,0,sizeof(svraddr));
   svraddr.sin_family=AF_INET;
   svraddr.sin_addr.s_addr=INADDR_ANY;
   //svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");第二种写法
   svraddr.sin_port=htons(5555);
   int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));
   if(ret<0)
   {
      error_exit("bind error");
   }
   //设置监听参数back login 半连接数最大
   ret=listen(sockfd,1024);
   if(ret<0)
   {
      error_exit("listen error");
   }
    //创建epoll监听集合
   int epfs=epoll_create(100);
   //添加svrfd
   struct epoll_event event;
   event.data.fd=sockfd;
   event.events=EPOLLIN;
   epoll_ctl(epfs,EPOLL_CTL_ADD,sockfd,&event);
   
   struct epoll_event ret_events[20]={0};
   int i=0;
   struct sockaddr_in removeaddr;
   memset(&removeaddr,0,sizeof(removeaddr));
   int addr_len=sizeof(removeaddr);
   char buf[1024]={0};
   while(1)
   {
        int nevent =epoll_wait(epfs,ret_events,20,-1);
        if(nevent<0)
        {
            error_exit("timeout\n");
        }
        else if(nevent==0)
        {
            printf("timeout\n");
            continue;
        }
        for(i=0;i<nevent;i++)
        {
            if(ret_events[i].data.fd== sockfd)
            {
                 int clientfd=accept(sockfd,(struct sockaddr*)&removeaddr,&addr_len);
                 if(clientfd<0)
                 {
                       error_exit("accept error");
                 }
                 printf("new connection fd %d\n",clientfd);
                 
                 event.data.fd=clientfd;
                 event.events=EPOLLIN;
                 epoll_ctl(epfs,EPOLL_CTL_ADD,clientfd,&event);
            }
            else if(ret_events[i].events&EPOLLIN)
            {
                 int rdsize=read(ret_events[i].data.fd,buf,1024);
                 if(rdsize<=0)
                 {
                     printf("close %d\n",ret_events[i].data.fd);
                     close(ret_events[i].data.fd);
                     event.data.fd=ret_events[i].data.fd;
                     event.events=EPOLLIN;
                     epoll_ctl(epfs,EPOLL_CTL_DEL,ret_events[i].data.fd,&event);      
                 }
                 else 
                 {
                     printf("read data %s\n",buf);
                 }
            }
        }
   }
   close(sockfd);
 }

客户端依旧是前面一篇的代码:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/types.h>  
#include <sys/wait.h>
#include <sys/socket.h>  
#include <netinet/in.h> /* for struct sockaddr_in*/  
#include <sys/errno.h>  
#include <signal.h> 

//关于客户端的socket
void error_exit(char *name)
{
   perror(name);
   exit(-1);
}
int main(int argc,char *argv[])
{
   if(argc<3)
   {
        printf("run program+ip+port\n");
        return-1;
   }
   int sockfd=socket(AF_INET,SOCK_STREAM,0);
   if(sockfd<0)
   {
        error_exit("create error");
   }
   //连接服务器,设置服务器的地址(ip和端口)
   struct sockaddr_in svraddr;
   memset(&svraddr,0,sizeof(svraddr));
   svraddr.sin_family=AF_INET;
   svraddr.sin_addr.s_addr= inet_addr(argv[1]);
   svraddr.sin_port=htons(atoi(argv[2]));
   int ret =connect(sockfd,(struct sockaddr *)&svraddr,sizeof(svraddr));
   if(ret<0)
   {
      error_exit("connect error");
   }

   write(sockfd,"hello",strlen("hello"));
   sleep(5); 
   close(sockfd);
   return 0;
}

先执行服务端,再执行客户端代码:

以下是客户端结果:

接下来查看服务端结果:

epoll总结:epoll其实有两种模式,EPOLLLT和EPOLLET,其实前面我们实现poll,select都是EPOLLLT模式,这篇也是。EPOLLLT为监听模式,数据在缓冲区读完,wait还是会触发。而EPOLLET数据哪怕读一点点,再次wait就不会触发,因为ET会从不可读变为可读,也只有这一过程wait才会触发,所以它只能触发一次。

这时候我们会想,EPOLLET只触发一次,那我们如何读取它所有的数据,所以我们实现的时候必须一次性读干净文件,不然它的文件描述符就不能用了。所以为了实现这样的方法,就要对文件设置为非阻塞。

下面为设置非阻塞代码:

void setnonblock(int fd)
{
   int flags = 0;
   fcntl(fd,F_GETFL,flags);
   flags |= O_NONBLOCK;
   fcntl(fd,F_SETFL,flags);
}

在设置非阻塞的时候,读的时候的错误必须避免,我们进行判断,以下是代码(errno为错误码,EAGAIN为无数据读的时候)

int n = 0;
while((nread = read(fd,buf+n,BUFSIZE-n))>0)
{
    n+=nread;
}
if(nread == -1 && errno != EAGAIN)
{
    perror("read error");
}

还有就是accept的错误,以下是代码

while(conn_sock=accept(listenfd,(struct sockaddr *)&removeaddr,(size_t *)addr_len))>0)
{
    handle_clinet(conn_sock);
}
if(conn_sock == -1)
{
    if(errno!=EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
              perror("error");
}

最后对select,poll,epoll三种实现方法进行总结:

select 监听的文件描述符为固定,哪怕产生一个,也要遍历全部,效率不高

poll   虽然文件描述符可以变大,但是是一个数组

epoll 每次读,只返回读的数量,效率高(而且ET效率比LT更高,不过ET编码要求高,而且一次读就要读干净)

猜你喜欢

转载自blog.csdn.net/Gaodes/article/details/82823651