poll,epoll,select,poll与epoll的区别,LT模式与ET模式的区别

poll,epoll,select,poll与epoll的区别,LT模式与ET模式的区别,第1张

文章目录
    • 一,poll
      • poll与select的区别
    • 二,epoll
      • select,poll 与epoll的区别
      • LT模式与ET模式的区别

一,poll

以下图片来自Liunx高性能服务器编程

poll与select的区别

poll是个加强版的select

poll能检测的文件描述符的数目更大(但是每个计算机都有自己的最大数量) poll注册的事件类型更多
poll既可以在liunx上可以,也可以在windows和unix上用
select是数组将文件描述符交给set_fd,在交给select,而poll直接将文件描述符交给poll



#define _GNU_SOURCE//POLLRDHUP的宏
#include
#include
#include
#include
#include
#include
#include
#include
#include


#define MAX 10

int socket_init();
void poll_fds_init(struct pollfd fds[])//将整个数组全部清零
{
   for(int i=0;i<MAX;++i)
   {
      fds[i].fd=-1;
      fds[i].events=0;
      fds[i].revents=0;
   }
}

void poll_fds_add(int fd,struct pollfd fds[])
{
    for(int i=0;i<MAX;++i)
    {
         if(fds[i].fd==-1)
         {
             fds[i].fd=fd;
             fds[i].events=POLLIN;//读事件
             fds[i].revents=0;//等到poll返回后poll会帮我们填充revents所以检查发生了哪些事件,就要检查revents中是否有我们注册的事件发生
             break;
         }
    }
}

void poll_fds_del(int fd,struct pollfd fds[])
{
    for(int i=0;i<MAX;++i)
    {
       if(fds[i].fd==fd)
       {
          fds[i].fd=-1;
          fds[i].events=0;
          fds[i].revents=0;
          break;
       }
    }
}

int main()
{
   int sockfd=socket_init();
   assert(sockfd!=-1);
   struct pollfd poll_fds[MAX];
   poll_fds_init(poll_fds);//清空数组
   poll_fds_add(sockfd,poll_fds);//

  while(1)
  {
     int n=poll(poll_fds,MAX,5000);//5000mspoll以ms为单位
     if(n<0)
     {
        printf("poll err\n");
     }
     else if(n==0)//poll返回值为0代表失败了
     {
         printf("time out\n");
     }
     else
     {
          for(int i=0;i<MAX;++i)//遍历数组
          {
                   if(poll_fds[i].fd==-1)
                   {
                             continue;
                   }
                   //如果没有以下代码,客端关闭但是服务器端依旧判断该描述符上有读事件,
                   if(poll_fds[i].revents & POLLRDHUP)//真则说明客户端已断开
                   {
                       close(poll_fds[i].fd);
                       poll_fds_del(poll_fds[i].fd,poll_fds);
                       printf("client hup\n");
                       continue;
                   }
                 
                   if(poll_fds[i].revents & POLLIN)//与,判断是否有读事件
                   {
                      if(poll_fds[i].fd==sockfd)
                      {
                          struct sockaddr_in caddr;//记录一个客户端ip接口的套接字地址结构
                          int len=sizeof(caddr);
                          int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
                          if(c<0)
                          {
                            continue;
                          }
                          printf("accept :%d\n",c);
                          poll_fds_add(c,poll_fds);
                      }
                      else
                      {
                          char buff[128]={0};
                          int num=recv(poll_fds[i].fd,buff,127,0);
                           // int num=recv(poll_fds[i].fd,buff,1,0);
                          if(num<=0)
                          {
                             close(poll_fds[i].fd);
                             poll_fds_del(poll_fds[i].fd,poll_fds);
                             printf("client close\n");
                          }
                          else
                          {
                             printf("recv(%d)=%s\n",poll_fds[i].fd,buff);
                             send(poll_fds[i].fd,"ok",2,0);
                          }
                      }
                   }
                   //if(poll_fds[i].revents*POLLOUT)
          }
     }
  }
}

int socket_init()
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);
   if(sockfd==-1)
   {
     return -1;
   }
   struct sockaddr_in saddr;
   memset(&saddr,0,sizeof(saddr));
   saddr.sin_family=AF_INET;
   saddr.sin_port=htons(6000);
   saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

   int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
   if(res==-1)
   {
      return -1;
   }

    res=listen(sockfd,5);
    if(res==-1)
    {
       return -1;
    }

   return sockfd;
   
}

                 

跟select一样客户端只发来了一段数据,但是服务器端没有一次性将数据读完,会继续触发读事件,poll会继续返回,客户端关闭,仍然会触发读事件

只接收一个数据时候的情况,和select情况一样,读了多少次就返回多少个OK

二,epoll

应对描述符多的

select,poll 与epoll的区别



以下图片来自Liunx高性能服务器编程





select和poll需要反复的将fd_set和fds里面的描述符交给内核空间(每一轮),在内核空间处理,每个描述符只添加一次
linux上特有的一种方法 epoll_create()创建内核事件表,存放描述符和事件(红黑树) epoll_ctl()添加,移除描述符
每个描述符只需添加一次 epoll_wait()返回就绪的描述符O(1)

day22
epoll代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAX 10

int socket_init();
void epoll_add(int epfd,int fd)
{
    struct  epoll_event ev;
    ev.data.fd=fd;
    ev.events=EPOLLIN|EPOLLEY;//读事件
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
    {
        printf("epoll ctl add err\n");
        
    }
}
void epoll_del(int epfd,int fd)
{
    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
    {
       perror("epoll ctl del err\n");
    }
}
int main()
{
   int sockfd=socket_init();
   assert(sockfd!=-1);
   int epfd=epoll_create(MAX);//内核事件表
   assert(epfd!=-1);
    epoll_add(epfd,sockfd);将监听套接字添加到内核事件表
    struct epoll_event evs[MAX];收集就绪描述符
    while(1)
    {
        int n=epoll_wait(epfd,evs,MAX,5000);//阻塞获取就绪描述符
        if(n==-1)
        {
           printf("epoll wait err\n");
        }else if(n==0)
        {
           printf("time out\n");
        }else
        {
           for(int i=0;i<n;++i)
           {
              int fd=evs[i].data.fd;
              if(evs[i].events & EPOLLIN)//有读事件
              {
                if(fd==sockfd)
                {
                   struct sockaddr_in caddr;
                   int len=sizeof(caddr);
                   int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
                   if(c<0)
                   {
                      continue;
                   }
                   printf("accept c=%d\n",c);
                   epoll_add(epfd,c);
                }else
                {
                  char buff[128]={0};
                  int num=recv(fd,buff,127,0);
                  if(num<=0)
                  {//注意顺序,必须先移除fd(移除时用到了该文件描述符,所以不能先close(fd),但是select和poll不必在意),在关闭fd
                     epoll_del(epfd,fd);
                     close(fd);
                     printf("client close\n");
                  }else
                  {
                      printf("recv(%d)=%s\n",fd,buff);
                      send(fd,"ok",2,0);
                  }
                }
              }
           }
        }
        
    }
}

int socket_init()
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);
   if(sockfd==-1)
   {
     return -1;
   }
   struct sockaddr_in saddr;
   memset(&saddr,0,sizeof(saddr));
   saddr.sin_family=AF_INET;
   saddr.sin_port=htons(6000);
   saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

   int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
   if(res==-1)
   {
      return -1;
   }

    res=listen(sockfd,5);
    if(res==-1)
    {
       return -1;
    ]

   return sockfd;
   
}

ET模式epoll代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX 10

int socket_init();
void setnonblock(int fd)
{
 int oldfl=fcntl(fd,F_GETFL);
 int newfl=oldfl|O_NONBLOCK;//设置为阻塞,然后循环读取
 if(fcntl(fd,F_SETFL,newfl)==-1)
 {
  printf("fcntl err\n");
 }
}
void epoll_add(int epfd,int fd)
{
    struct  epoll_event ev;
    ev.data.fd=fd;
    ev.events=EPOLLIN|EPOLLEY;//读事件
    //ev.events=EPOLLIN|EPOLLEY;//读事件|ET模式的设置
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
    {
        printf("epoll ctl add err\n");
    }
    setnonblock(fd);
}
void epoll_del(int epfd,int fd)
{
    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
    {
       perror("epoll ctl del err\n");
    }
}
int main()
{
   int sockfd=socket_init();
   assert(sockfd!=-1);
   int epfd=epoll_create(MAX);//内核事件表
   assert(epfd!=-1);
    epoll_add(epfd,sockfd);将监听套接字添加到内核事件表
    struct epoll_event evs[MAX];收集就绪描述符
    while(1)
    {
        int n=epoll_wait(epfd,evs,MAX,5000);//阻塞获取就绪描述符
        if(n==-1)
        {
           printf("epoll wait err\n");
        }else if(n==0)
        {
           printf("time out\n");
        }else
        {
           for(int i=0;i<n;++i)
           {
              int fd=evs[i].data.fd;
              if(evs[i].events & EPOLLIN)//有读事件
              {
                if(fd==sockfd)
                {
                   struct sockaddr_in caddr;
                   int len=sizeof(caddr);
                   int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
                   if(c<0)
                   {
                      continue;
                   }
                   printf("accept c=%d\n",c);
                   epoll_add(epfd,c);
                }else
                {
                  while(1)
                  {
                     char buff[128]={0};
                     int num=recv(fd,buff,1,0)l
                     if(num==-1)
                     {
                       if(errno==EAGAINT ||erno== EWOULDBLOCK)//等于他就说明非阻塞模式下,没有可读的数据产生的错误
                       {
                        send(fd,"ok",2,0);
                       }
                       else
                       {
                         printf("recv err");
					   }
                     }
                      else if(num==0)
                      {
                        epoll_del(epdf,fd);
                        close(fd);
                        printf("client close\n");
                        break;
					  }
					  else
					  {
					   printf("buff=%s\n",buff);
					  }
                  }
                }
              }
           }
        }
        
    }
}

int socket_init()
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);
   if(sockfd==-1)
   {
     return -1;
   }
   struct sockaddr_in saddr;
   memset(&saddr,0,sizeof(saddr));
   saddr.sin_family=AF_INET;
   saddr.sin_port=htons(6000);
   saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

   int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
   if(res==-1)
   {
      return -1;
   }

    res=listen(sockfd,5);
    if(res==-1)
    {
       return -1;
    ]

   return sockfd;
   
}

LT模式与ET模式的区别

LT模式:select poll epoll都具有(读数据时,如果没读完会不停的提醒)
ET模式:只会提示一次数据来了(不管是否读完),只有epoll有
ET模式代码中,描述符设置为非阻塞,读取为循环读取

LT 有事件就绪 用户如果没有处理完,还会继续提醒
ET有就绪事件,只提醒用户一次(可能会丢数据)

epoll也是实现I/O多路复用的一种方法。


epoll水平触发(level
trigger,LT,LT为epoll的默认工作模式)与边缘触发(edge
trigger,ET)两种工作模式。


使用脉冲信号来解释LT和ET可能更加贴切。


Level是指信号只需要处于水平,就一直会触发;而edge则是指信号为上升沿或者下降沿时触发。


LT:只要内核缓冲区有数据就一直通知,只要socket处于可读状态或可写状态,就会一直返回sockfd;

ET:只有状态发生变化才通知,只有当socket由不可写到可写或由不可读到可读,才会返回其sockfd;

注:(在添加EPOLLIN、EPOLLOUT事件的前提下。


)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/569557.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存