Select模型

select模型

在单进程和单线程的C/S模型中,server会阻塞在accept和recv,而accept和recv二者有着相似的原理,即二者都在listen:
accept通过serverfd阻塞等待来自client的读事件,当收到读事件之后,accept则会唤醒完成连接
对于recv,其通过clientfd阻塞等待来自client的数据,当收到数据之后,recv则会唤醒读取数据
由于二者的特性,就导致了不能连接多个client。
通过引入select模型,可以将二者所监听的读事件都加入到一个集合中,由内核负责listen

代码

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<ctype.h>
  5 #include<sys/types.h>
  6 #include<sys/socket.h>
  7 #include<strings.h>
  8 #include<sys/select.h>
  9 #include<errno.h>
 10 #include<arpa/inet.h>
 11 #define DEFPORT 8000
 12 int main(void)
 13 {
 14     int sfd,cfd;//serverfd和clientfd
 15     int maxfd;//监听集合的最大值
 16     int clientsock[1024];//client监听集合
 17     int i,j,nReady,len,datalen;
 18     char buf[1024];//读取数据缓冲区
 19     bzero(buf,sizeof(buf));//缓冲区置0
 20     struct sockaddr_in server_sock,client_sock;
 21     fd_set rset,allset;//监听集合
 22     bzero(&server_sock,sizeof(server_sock));
 23     server_sock.sin_family = AF_INET;//IPV4
 24     server_sock.sin_port = htons(DEFPORT);//htons:本机序转网络序,16位short,端口号8000
 25     server_sock.sin_addr.s_addr = htonl(INADDR_ANY);//本机任意ip
 26     sfd = socket(AF_INET,SOCK_STREAM,0);
 27     bind(sfd,(struct sockaddr*)&server_sock,sizeof(server_sock));
 28     listen(sfd,128);
 29 
 30     maxfd = sfd;//开始将sfd作为最大值,因为sfd是监听集合第一个元素
 31     for(i=0;i<1024;i++)//将client监听集合所有元素初始化为-1,方便后续操作
 32         clientsock[i] = -1;
 33     FD_ZERO(&allset);//将监听集合元素置0
 34     FD_SET(sfd,&allset);//将allset的第sfd位元素置为1
 35     while(1)//此时设置一直监听,退出条件为监听集合数超过1024
 36     {
 37         rset = allset;//此时需要用rset来代替,是一个传入传出参数
 38         nReady = select(maxfd+1,&rset,NULL,NULL,NULL);//nReady为监听到的读事件数量
 39         while(nReady)//每次循环处理一个读事件
 40         {
 41         	if(FD_ISSET(sfd,&rset))//如果此时读事件是sfd相应,即accept接收到client的连接请求
 42             {
 43                 len = sizeof(client_sock);
 44                 cfd = accept(sfd,(struct sockaddr*)&client_sock,&len);//连接成功,返回cfd
 45                 for(i = 0; i < 1024; i++)
 46                     if(clientsock[i]==-1)//将cfd保存
 47                     {
 48                         clientsock[i] = cfd;
 49                         break;
 50                     }
 51                 if(i==1024)//此时i==1024,即达到select处理上线,退出,正常的话不应该退出
 52                     perror("client too much\n");
 53                 FD_SET(cfd,&allset);//将cfd加入监听集合
 54					FD_CLR(sfd,&rset);//将sfd从rset集合中删除,否则下次遍历时仍会进入当前if,会阻塞在accept
 55                 if(cfd>maxfd)//更新最大值位置
 56                     maxfd = cfd;
 57             }
 58             else//此时监听事件为recv的读事件,即接收到数据请求
 59             {
 60                for(i = 0; i < 1024; i++)//遍历找到对应读事件的cfd
 61			   		{
 62                     if(clientsock[i]!=-1)
 63                     {
 64                         if(FD_ISSET(clientsock[i],&rset))
 65                         {
 66                             if((datalen = recv(clientsock[i],buf,sizeof(buf)  ,0)>0))//接收到数据
 67                             {//业务处理
 68                                 printf("len=%d\n",len);
 69                                 for(j = 0; j < datalen; j++)
 70                                     buf[j] = toupper(buf[j]);
 71                                 printf("len=%d\n",datalen);
 72                                 send(clientsock[i],buf,len,0);
 73                                 bzero(buf,sizeof(buf));
 74                             }
 75                             if(len==0)//返回0,客户端断开连接
 76                             {//<0出错 =0断开连接 >0接收数据大小
 77                                FD_CLR(clientsock[i],&allset);//将断开连接的cfd从监听集合中删除
 78                                FD_CLR(clientsock[i],&rset); 
 79								   close(cfd);//关闭
 80                                clientsock[i] = -1;//将对应位置置为-1
  }
 80                             FD_CLR(clientsock[i],&rset);//每次处理完当前recv读事件,需要将其从rset集合中删除,否则下次仍会执行
 81								break;//处理完当前的recv,break
 82                         }
 83                     }
 84                 }
 85             }
 86             --nReady;//每次将nReady--
 87         }
 88     }
 89     return 0;
 90 }

设置rset和allset的原因

rset作为一个传入传出参数,当传入时,可以将监听集合利用select模型进行监听,传出时作为收到读事件的集合,如果只有一个allset,没有rset,那么会出现错误:
1.如果当client中有一个异常断开,而此时又有一个新的client连接,则会占用原来的clientsock的位置,但是此时循环的次数为nReady次数,那么此时则不会处理allset中最后一个读事件请求,因为循环次数固定,而循环内处理数量为nReady+1,则会出现错误。
2.当处理完accept的读事件之后,需要将其从rset集合中删除,否则当下次判断FD_ISSET(sfd,&rset)时,仍会判断为真,会进入if语句等待阻塞等待accept,出现错误。
同理,当处理recv的读事件时,每次处理完之后需要将对应clientsock[i]从监听rset中删除,否则会出现和上述一样的错误。
3.暂时就想到这种情况,后续遇到再补充
4.api直接参考manpage

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值