select , poll , epoll服务器应用解析

select poll epoll用法解析

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

select

man手册中select函数的介绍
在这里插入图片描述

  • 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是另一种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结构体数组可以重复利用
  • 同时连接到服务器的客户端的数目大大增加
    == 注意,以上的代码存在一个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做了重新排序
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百万攻城狮

你的鼓励是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值