select poll epoll用法示例

引用文章:
https://blog.csdn.net/caspar_notes/article/details/106991119
https://blog.csdn.net/weixin_39829031/article/details/104456309

select poll epoll用法示例
通常,服务器分为循环服务器和并发服务器,循环服务器每次只能处理一个来自客户端的请求连接,直到该客户端完成所有的请求断开连接之后才能开始下一个客户端的服务。并发服务器可以同时处理多个客户端的请求,实现并发的方式有多线程,多进程方式,但是开辟多个线程或者多个进程虽然可以满足要求,但是性能不够好,系统开销比较大。因此,我们采用IO多路复用技术来实现并发。IO多路复用方式有三种,select , poll , epoll。

select

select函数接口:

#include <sys/select.h>
#include <sys/time.h>
 
#define FD_SETSIZE 1024
#define NFDBITS (8 * sizeof(unsigned long))
#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)
 
// 数据结构 (bitmap)
typedef struct {
    unsigned long fds_bits[__FDSET_LONGS];
} fd_set;
 
// API
int select(
    int max_fd, 
    fd_set *readset, 
    fd_set *writeset, 
    fd_set *exceptset, 
    struct timeval *timeout
)                              // 返回值就绪描述符的数目
 
FD_ZERO(int fd, fd_set* fds)   // 清空集合
FD_SET(int fd, fd_set* fds)    // 将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)  // 判断指定描述符是否在集合中 
FD_CLR(int fd, fd_set* fds)    // 将给定的描述符从文件中删除  

select代码示例:

 #include<stdio.h>
 11 #include <sys/types.h>          /* See NOTES */
 12 #include <sys/socket.h>
 13 #include <string.h>
 14 #include <netinet/in.h>
 15  /* According to POSIX.1-2001 */
 16 #include <sys/select.h>
 17 /* According to earlier standards */
 18 #include <sys/time.h>
 19 #include <unistd.h>
 20 #define BUF_SIZE 100
  int main()
 24 {
 25    //socket
 26    int iServer=socket(AF_INET,SOCK_STREAM,0);
 27    if(-1==iServer)
 28    {
 29        printf("create socket error\r\n");
 30        return -1;
 31    }
 32    printf("create socket ok,iServer=%d\r\n",iServer);
 33    //bind
 34    struct sockaddr_in stServer;
 35    stServer.sin_family=AF_INET;
 36    stServer.sin_port=htons(8888);
 37    stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
 38    int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
   if(-1==ret)
 40    {
 41        printf("bind error\r\n");
 42        return -1;
 43    }
 44    printf("bind ok\r\n");
 45    //listen
 46    ret=listen(iServer,5);
 47    if(-1==ret)
 48    {
 49          return -1;
 50    }
 51    printf("listen ok\r\n");
 /*
	前面的操作我们创建了一个socket套接字,并与主机的ip地址绑定,设定
	了一个端。之后我们将此套接字设置为监听模式,监听队列的大小为5,下 
	面的例子即是select实现并发服务器的核心代码
*/
  //accept
 53    struct sockaddr_in stClient;
 54    socklen_t len=sizeof(struct sockaddr_in);
 55    char buf[BUF_SIZE];
 56    fd_set stFdr; // 定义一个fd_set类型的变量,用于添加socket套接字描述符
 57    FD_ZERO(&stFdr); // 清空stFdr
 58    FD_SET(iServer,&stFdr); // 把监听套接字描述符加入stFdr中
 59    int Max=iServer; //select 最大的监听套接字描述符
 60    while(1)
 61    {
 62         fd_set stFdrTmp=stFdr; //将stFdr赋值给临时的变量,因为程序会修改stFdrTmp
 63         ret=select(Max+1,&stFdrTmp,NULL,NULL,NULL);//监听最大的描述符为 Max + 1 ,临时的stFdrTmp,select是阻塞函数,如果有某个socket描述符有数据,即可返回,返回值为有数据的socket描述符的个数
 64         if(ret<=0)
 65         {
 66              printf("select error\r\n");
 67              continue;
 68         }
 69         printf("select ok,ret=%d\r\n",ret);
 71         int i=0;
            // 循环遍历每一个socket描述符,看一下哪一个被置位
 72         for(;i<Max+1;i++)
 73         {
                //如果当前的文件描述符被置位
 74             if(FD_ISSET(i,&stFdrTmp))
 75             {
 					 // 如果是服务器套接字,就接收一个新的连接,并把这个连接加入到stFdc中
 76                  if(i==iServer)
 77                  {
 78                       int iClient=accept(iServer,(struct sockaddr *)&stClient,&len);
 79                       if(-1==iClient)
 80                       {
 81                           continue;
 82                       }
 83                       printf("iclient=%d\r\n",iClient);
 84                       FD_SET(iClient,&stFdr); //添加新的描述符
 85                       if(Max<iClient)
 86                       {
 87                           Max=iClient; //设置监听的最大值
 88                       }
 89                }
                   else
 91                {                                                      
                         // 单独处理每一个连接的事务
                         ret=recv(i,buf,BUF_SIZE,0);
 93                      printf("recv data:%s\r\n",buf);
 94                      if(ret>0)
 95                      {                                                 send(i,buf,BUF_SIZE,0);
 97                      }
 98                      else
 99                      {
100                          close(i);
101                          FD_CLR(i,&stFdr);
102                      }
103                 }
104               }
105           }
106         }
107    return 0;
108 }                                    

select的特点:

  • 1024的bitmap,因此select服务器只能同时处理1024个客户端的连接
  • FDset 不可重用
  • select维护1024位的bitmap,使用时从用户空间拷贝到内核空间,然后内核检测IO的数据情况,置位 bitmap,然后又从内核空间返回到用户空间
  • 用户处理select返回,需要遍历bitmap,才能知道哪一个IO被置位,这个过程需要O(n)的时间复杂度

poll

poll函数接口:

#include <poll.h>
// 数据结构
struct pollfd {
    int fd;                         // 需要监视的文件描述符
    short events;                   // 需要内核监视的事件
    short revents;                  // 实际发生的事件
};
 
// API
int poll(struct pollfd fds[], nfds_t nfds, int timeout);

poll是另一种IO多路复用技术
poll代码示例:

#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
 /* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include <poll.h>
#define BUF_SIZE 100
/*
struct pollfd
{
	int fd;
	short events;
	short revents;
};
*/
struct Polldata
{
   struct pollfd pollfds[BUF_SIZE];
   int size;
}list;
/*这里我们定义了一个Polldata类型的数据结构,其中包含两个数据成员,
  一个是struct pollfd类型的数组,数组大小为BUF_SIZE,这个长度决定了
  我们允许同时连接到该服务器的客户端的个数最大为MAX_SIZE,size成员
  表示了我们目前连接到服务器的客户端的个数。
*/
int main()
{
   //socket
   int iServer=socket(AF_INET,SOCK_STREAM,0);
   if(-1==iServer)
   {
       printf("create socket error\r\n");
       return -1;
   }
   printf("create socket ok,iServer=%d\r\n",iServer);
   //bind
   struct sockaddr_in stServer;
   stServer.sin_family=AF_INET;
   stServer.sin_port=htons(8888);
   stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
   int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
   if(-1==ret)
   {
	   printf("bind error\r\n");
	   return -1;
   }
   printf("bind ok\r\n");
   //listen
   ret=listen(iServer,5);
   if(-1==ret)
   {
	   return -1;
   }
   printf("listen ok\r\n");
   //之前我们创建了一个socket套接字,绑定了一个主机地址和端口号,并把该套接字设置为监听模式,监听队列的大小为5
   //accept
   struct sockaddr_in stClient;
   socklen_t len=sizeof(struct sockaddr_in);
   memset( &list , 0 , sizeof(list) );
   char buf[BUF_SIZE];
   int stlen;
   int i;
   int listSize;
   list.size = 1;
   list.pollfds[0].fd = iServer;
   list.pollfds[0].events = POLLIN;
   /*
		我们将iServer加入监听队列,并初始化list的size为1
   */
   while(1)
   {
    puts( "rount again" );
	listSize = list.size;
	printf( "total client %d\n" , listSize - 1 );
	poll( list.pollfds , listSize , -1 );
	/*
	用poll函数,传参为pollfds的数组首地址,元素个数为当前加入监听的     
    数量,-1表示阻塞状态监听
    */
    /*
		循环遍历加入其中的元素,根据revents是否被内核置位来判断哪个IO口有消息输入
    */
	for( i = 0 ; i < listSize ; i++ )
	{
	     if( list.pollfds[i].revents )
	     {
	     /*
	       如果是iServer套接字,则表示新的客户端请求连接服务器
           我们就要处理这个新的连接
		*/
		     if( i == 0 )
		     {
		     if( list.size <= BUF_SIZE )
		     {
	             	 memset( &stClient , 0 , len );
                         ret  = accept( iServer , (struct sockaddr*)&stClient , &stlen );
			 list.pollfds[list.size].fd = ret;
			 list.pollfds[list.size].events = POLLIN;
			 list.size++;
		     }
		     else
		     {
			 printf( "maxinum connections\n" );
		     }  
		 }
		 else
		 {
		 /*
			这里表示一个普通的客户端发消息给服务器,服务器接收这个消息即可
		*/
		    memset( buf , 0 , BUF_SIZE );
		    ret = recv( list.pollfds[i].fd , buf , BUF_SIZE , 0 );
                    if( ret <= 0 )
		    {
			printf( "recv %d error\n" , list.pollfds[i].fd );
			close( list.pollfds[i].fd );
		    }		    
		    ret = send( list.pollfds[i].fd , buf , BUF_SIZE , 0 );
		    if( ret <= 0 )
		    {
			printf( "send %d errror\n" , list.pollfds[i].fd );
			close( list.pollfds[i].fd );
		    }
		    list.pollfds[i].revents = 0;
		    //给pollfd中的成员revents置位
		 }
	     }
	}
   }
   return 0;
}


poll的特点:

  • 存在用户态到内核态的pollfd 结构体的传递和置位
  • 需要轮询pollfd 结构体数组才能知道哪一个被置位
  • pollfd结构体数组可以重复利用
  • 同时连接到服务器的客户端的数目大大增加

epoll

epoll函数接口:

#include <sys/epoll.h>
 
// 数据结构
// 每一个epoll对象都有一个独立的eventpoll结构体
// 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
// epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可
struct eventpoll {
    /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
    struct rb_root  rbr;
    /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
    struct list_head rdlist;
};
 
// API
 
int epoll_create(int size); // 内核中间加一个 ep 对象,把所有需要监听的 socket 都放到 ep 对象中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl 负责把 socket 增加、删除到内核红黑树
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 负责检测可读队列,没有可读 socket 则阻塞进程

epoll对poll做了相应的改进先看代码示例:

#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
 /* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
struct Polldata
{
   struct epoll_event events[BUF_SIZE];
   int size;
}list;
int main()
{
   //socket
   int iServer=socket(AF_INET,SOCK_STREAM,0);
   if(-1==iServer)
   {
       printf("create socket error\r\n");
       return -1;
   }
   printf("create socket ok,iServer=%d\r\n",iServer);
   //bind
   struct sockaddr_in stServer;
   stServer.sin_family=AF_INET;
   stServer.sin_port=htons(8888);
   stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
   int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
   if(-1==ret)
   {
	   printf("bind error\r\n");
	   return -1;
   }
   printf("bind ok\r\n");
   //listen
   ret=listen(iServer,5);
   if(-1==ret)
   {
	   return -1;
   }
   printf("listen ok\r\n");
   //accept
   struct sockaddr_in stClient;
   socklen_t len=sizeof(struct sockaddr_in);
   memset( &list , 0 , sizeof(list) );
   char buf[BUF_SIZE];
   int stlen;
   int i;
   int listSize;
   struct epoll_event ev;
   int epfd = epoll_create( 10 );
   ev.data.fd = iServer;
   ev.events = EPOLLIN;
   epoll_ctl( epfd , EPOLL_CTL_ADD , iServer , &ev );
   list.size = 1;
   while(1)
   {
        puts( "rount again" );
	listSize = list.size;
	printf( "total client %d\n" , listSize - 1 );
	int nfds = epoll_wait( epfd , list.events , list.size , -1 );
	for( i = 0 ; i < nfds ; i++ )
	{
		 if( list.events[i].data.fd == iServer )
		 {
		     if( list.size <= BUF_SIZE )
		     {
	             	 memset( &stClient , 0 , len );
                         ret  = accept( iServer , (struct sockaddr*)&stClient , &stlen );
			 if( ret <= 0  )
			 {
			     printf( "accept error\n" );
			     continue;
			 }
			 ev.data.fd = ret;
			 ev.events =  EPOLLIN;
			 epoll_ctl( epfd , EPOLL_CTL_ADD , ret , &ev );
			 list.size++;
		     }
		     else
		     {
			 printf( "maxinum connections\n" );
		     }  
		 }
		 else
		 {
		    printf( "read from client\n" );
		    memset( buf , 0 , BUF_SIZE );
		    ret = recv( list.events[i].data.fd , buf , BUF_SIZE , 0 );
                    if( ret <= 0 )
		    {
			printf( "recv %d error\n" , list.events[i].data.fd );
			close( list.events[i].data.fd );
		    }		    
		    ret = send( list.events[i].data.fd , buf , BUF_SIZE , 0 );
		    if( ret <= 0 )
		    {
			printf( "send %d errror\n" , list.events[i].data.fd );
			close( list.events[i].data.fd );
		    }
		 }
	     
	}
   }
   return 0;
}

poll的特点:

  • 存在用户态到内核态的pollfd 结构体的传递和置位
  • 需要轮询pollfd 结构体数组才能知道哪一个被置位
  • pollfd结构体数组可以重复利用
  • 同时连接到服务器的客户端的数目大大增加
    == 注意,以上的代码存在一个bug,运行时可以自行测试,时间原因我没有处理这个bug,欢迎私信交流==
    epoll
    epoll对poll做了相应的改进先看代码示例:
#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
 /* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
struct Polldata
{
   struct epoll_event events[BUF_SIZE];
   int size;
}list;
int main()
{
   //socket
   int iServer=socket(AF_INET,SOCK_STREAM,0);
   if(-1==iServer)
   {
       printf("create socket error\r\n");
       return -1;
   }
   printf("create socket ok,iServer=%d\r\n",iServer);
   //bind
   struct sockaddr_in stServer;
   stServer.sin_family=AF_INET;
   stServer.sin_port=htons(8888);
   stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
   int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
   if(-1==ret)
   {
	   printf("bind error\r\n");
	   return -1;
   }
   printf("bind ok\r\n");
   //listen
   ret=listen(iServer,5);
   if(-1==ret)
   {
	   return -1;
   }
   printf("listen ok\r\n");
   //accept
   struct sockaddr_in stClient;
   socklen_t len=sizeof(struct sockaddr_in);
   memset( &list , 0 , sizeof(list) );
   char buf[BUF_SIZE];
   int stlen;
   int i;
   int listSize;
   struct epoll_event ev;
   int epfd = epoll_create( 10 );
   ev.data.fd = iServer;
   ev.events = EPOLLIN;
   epoll_ctl( epfd , EPOLL_CTL_ADD , iServer , &ev );
   list.size = 1;
   while(1)
   {
        puts( "rount again" );
	listSize = list.size;
	printf( "total client %d\n" , listSize - 1 );
	int nfds = epoll_wait( epfd , list.events , list.size , -1 );
	for( i = 0 ; i < nfds ; i++ )
	{
		 if( list.events[i].data.fd == iServer )
		 {
		     if( list.size <= BUF_SIZE )
		     {
	             	 memset( &stClient , 0 , len );
                         ret  = accept( iServer , (struct sockaddr*)&stClient , &stlen );
			 if( ret <= 0  )
			 {
			     printf( "accept error\n" );
			     continue;
			 }
			 ev.data.fd = ret;
			 ev.events =  EPOLLIN;
			 epoll_ctl( epfd , EPOLL_CTL_ADD , ret , &ev );
			 list.size++;
		     }
		     else
		     {
			 printf( "maxinum connections\n" );
		     }  
		 }
		 else
		 {
		    printf( "read from client\n" );
		    memset( buf , 0 , BUF_SIZE );
		    ret = recv( list.events[i].data.fd , buf , BUF_SIZE , 0 );
                    if( ret <= 0 )
		    {
			printf( "recv %d error\n" , list.events[i].data.fd );
			close( list.events[i].data.fd );
		    }		    
		    ret = send( list.events[i].data.fd , buf , BUF_SIZE , 0 );
		    if( ret <= 0 )
		    {
			printf( "send %d errror\n" , list.events[i].data.fd );
			close( list.events[i].data.fd );
		    }
		 }
	     
	}
   }
   return 0;
}

epoll的特点:

  • 采用了epfd方式,节省了用户态与内核态之间交互的开销
  • 不需要轮询,epoll_wait函数返回的是当前产生事件的描述符的个数,并且对epfd做了重新排序
posted @ 2021-08-26 16:12  一只小菜菜鸟  阅读(377)  评论(0)    收藏  举报