linux网络编程之多路IO转接----select

上一篇博客我们通过多进程和多线程可以为多个客户端提供服务,但是开多个进程或者线程消耗也增多了。其实我们只要一个进程就可以实现对多个客户端的监听,那就是多路IO复用,今天介绍其中的select函数(因为poll和epoll我还没学到哈哈)。

函数原型int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

nfds为 最大文件描述符+1,因为函数内部要对监听的文件描述符遍历,需要for循环上限(最大文件描述符+1)

readfds为 读文件描述符集,其类型是fd_set,其实这个和信号机制中的信号屏蔽字比较像,是用位图存储信息,每个二进制位表示该位的文件描述符是否存在(1为存在 ,0为不存在)。
writefds为 写文件描述符集,exceptfds为 异常文件描述符集,这三个文件描述符集为传入传出参数,传入的是需要负责监听的文件描述符,传出的是发生事件的文件描述符
其虽然为位图的数据结构,但我们并不能对其位进行操作,而需要使用系统为我们提供的操作函数来操作:

       void FD_CLR(int fd, fd_set *set);//将文件描述符从set中去除
       int  FD_ISSET(int fd, fd_set *set);//判断文件描述符是否在set中
       void FD_SET(int fd, fd_set *set);//将文件描述符加入到set中
       void FD_ZERO(fd_set *set);//将set清零,即每位置0

timeout为select阻塞等待时长,传NULL表示阻塞,传0表示非阻塞,传值大于0值则等待固定时长。
其结构体内容为:

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

返回值:成功返回三个文件描述符集中包含的文件描述符个数。

一般都是客户端向服务端发送建立连接请求或者发送数据,服务端对应的第一处理动作是读,所以一般我们对select的 读文件描述符集应用较多,没有特殊需求一般writefds和exceptfds两个参数都传NULL。

需要说明的是,select函数的功能只是让内核帮我们监听客户端的请求,相应的处理还是需要我们在代码实现

下面通过代码说明select函数的一般用法:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/select.h>
  4 #include<arpa/inet.h>
  5 #include<ctype.h>
  6 #include"wrap.h"
  7 #define SERVER_PORT 6666
  8 int main(void)
  9 {
 10     int lfd,cfd,maxfd,ret;
 11     fd_set rset,allset;
 12     char buf[BUFSIZ];
 13     int fds[1024],maxi = -1;
 14 
 15     lfd = Socket(AF_INET,SOCK_STREAM,0);//建立套接字
 16 
 17     struct sockaddr_in serv_addr,client_addr;
 18     socklen_t client_addr_len = sizeof(client_addr);
 19 
 20     //绑定ip和port
 21     serv_addr.sin_family = AF_INET;
 22     serv_addr.sin_port = htons(SERVER_PORT);
 23     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 24     Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
 25 
 26     Listen(lfd,128);//设置允许同时连接上限
 27 
 28     FD_ZERO(&allset);//将allset集合清零
 29     FD_SET(lfd,&allset);//将lfd加入到allset集合中
 30     maxfd = lfd;
 31 
 32     for(int i = 0; i < 1024;i++)//将fds数组初始化,1024为文件描述符的上限
 33         fds[i] = -1;
 34     while(1)
 35     {
 36         rset = allset;//因为每次select都会将rset改变,所以需要将所有文件描述符赋值给他  
 37         ret = select(maxfd+1,&rset,NULL,NULL,NULL);
 38         if(ret < 0)
 39             perr_exit("select error");
 40         if(FD_ISSET(lfd,&rset))
 41         {
 42             cfd = accept(lfd,(struct sockaddr
 43                         *)&client_addr,&client_addr_len);
 44             FD_SET(cfd,&allset);
 45             for(int i = 0; i < 1024 ;i++)//将数据通信的文件描述符存到fds数组中
 46             {
 47                 if(fds[i] == -1)
 48                 {
 49                     fds[i] = cfd;
 50                     if(maxi < i)//保存最大下标
 51                         maxi = i;
 52                     break;
 53                 }
 54             }
 55             if(maxfd < cfd)//保存最大文件描述符
 56                 maxfd = cfd;
 57 
 58             if(--ret == 0)//若只返回一个文件描述符,且为lfd,则无需执行后续代码
 59                 continue;
 60         }
 61         for(int i = 0;i <= maxi;i++)//遍历文件描述符数组,看那个文件描述符在返回的rset集合中
 62         {
 63             if(fds[i] == -1)//跳过为空的数组单元
 64                 continue;
 65             if(FD_ISSET(fds[i],&rset))//若存在则处理客户端请求
 66             {
 67                 int n = Read(fds[i],buf,sizeof(buf));
 68                 if(n == 0)
 69                 {
 70                     close(fds[i]);//关闭文件描述符
 71                     FD_CLR(fds[i],&allset);//将文件描述符从allset集合中去除
 72                     fds[i] == -1;//将文件描述符从fds数组中去除
 73                 }
 74                 for(int i = 0;i < n;i++)
 75                     buf[i] = toupper(buf[i]);
 76                 Write(fds[i],buf,n);
 77                 if(--ret == 0)//若已经将返回的读文件描述符处理完了,则不用继续向后循环
 78                     break;
 79             }
 80         }
 81     }
 82     return 0;
 83 }

其中头文件wrap.h是对socket建立通信中常用到的函数的一个出错处理封装,上篇博客里有对应的.c和.h代码,这里就不再给出。

发布了31 篇原创文章 · 获赞 4 · 访问量 946

猜你喜欢

转载自blog.csdn.net/qq_39781096/article/details/104577437