linux网络编程 多路IO转接服务器 select

多路IO转接服务器:不由应用程序自己监听客户端连接,取而代之由内核替应用程序监听文件

  • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024
  • 超过限制值后,select采用轮询模型,会降低服务器响应效率
   1 /*
  2 头文件:#include <sys/select.h>
  3 原型:
  4 int select(int nfds, fd_set *restrict readfds,fd_set *restrict writefds, fd_set *restrict errorfds,struct timeval *restrict timeout);
  5 nfds:          最大描述符+1
  6 readfds:        可读事件
  7 writefds:       可写事件
  8 errorfds:       异常事件
  9 timeout:        设置监听的时间  
 10 
 11 返回值:成功返回满足条件的总数;失败返回-1,设置errno
 12 作用:判断集合中是否有对应的文件描述符发送数据
 13 
 14 
 15 fd_set set;     设置文件描述符集
 16 void FD_CLR(int fd,fd_set *set);        将fd从set中清除
 17 void FD_ZERO(fd_set *set);              将set清空
 18 void FD_SET(int fd,fd_set *set);        将将fd设置到set集合中去
 19 
 20 int  FD_ISSET(int fd,fd_set *set);      判断fd是否在集合中
 21 返回值:成功返回0;失败返回-1,设置errno
 22 作用:设置文件描述符集合内的成员
 23 
 24 struct timespec {
 25     time_t      tv_sec;          seconds 
 26     long        tv_nsec;         nanoseconds 
 27 };
 28 struct timeval {
 29     time_t      tv_sec;          seconds 
 30     suseconds_t tv_usec;         microseconds 
 31 };
 32 */

服务器监听、对可读事件的操作:


 34 #include<stdio.h>
 35 #include<sys/socket.h>
 36 #include<arpa/inet.h>
 37 #include<strings.h>
 38 #include<unistd.h>
 39 #include<ctype.h>
 40 #include"fork.h"
 41 #define SERV_PORT 8888
 42 #define SERV_ADDR "127.0.0.1"
 43 void main(void)
 44 {
 45         //定义listennfd套接字
 46         int nfds,listenfd,connfd,sockfd;
 47         listenfd=socket(AF_INET,SOCK_STREAM,0);
 48         char buf[BUFSIZ];
 49 
 50         struct sockaddr_in serv_sockaddr,clie_sockaddr;
 51         socklen_t clie_sockaddr_len=sizeof(clie_sockaddr);
 52         bzero(&clie_sockaddr,sizeof(clie_sockaddr));
 53         bzero(&serv_sockaddr,sizeof(serv_sockaddr));
 54         serv_sockaddr.sin_family=AF_INET;
 55         serv_sockaddr.sin_port=htons(SERV_PORT);
 56         serv_sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 57         //绑定listenfd套接字
 58         bind(listenfd,(struct sockaddr *)&serv_sockaddr,sizeof(serv_sockaddr));
 59         //进行监听
 60         listen(listenfd,128);
 61         //select转接内核负责监听,并返回请求连接的信息
 62         //client[]将所添加的监听文件描述符放入,以便搜索可连接的sock文件描述符
 63         int client[FD_SETSIZE];
 64         //readset用于添加读事件文件描述符oldset用于保存readset的设置
 65         //(调用select会将readset不满足的描述符删除)
 66         fd_set readset,oldset;
 67         //起初listenfd即为最大文件描述符
 68         nfds=listenfd;
 69         //maxno作为client[]的下标
 70         int maxno=-1;
 71         //用-1初始化client[]
 72         int i;
 73         for(i=0;i<FD_SETSIZE;i++){
 74                 client[i]=-1;
 75         }
 76         //初始化oldset,并添加listenfd文件描述符到oldset保存
 77         FD_ZERO(&oldset);
 78         FD_SET(listenfd,oldset);
 79         //每次监听都重新设置readset内的所需监听的文件描述符
 80         //request=select();查看readset中是否有我监听的那个客户端请求连接
 81         int request;
 82         while(1){
 83                 readset=oldset;
 84                 request=select(nfds+1,&readset,NULL,NULL,NULL);
 85                 if(request<0){//出错打印,退出
 86                         perror("select error\n");
 87                         exit(1);
 88                 }
 89         //判断是否有客户端发送请求,有就把客户端的文件描述符放到oldset中
 90                 if(FD_ISSET(listenfd,&readset)){
 91         //当listenfd在readset集合中则表示有对应的客户端请求连接,存在才接收,所以accpet不阻塞
 92                         connfd=accept(listenfd,(struct sockaddr *)&clie_sockaddr,&clie_sockaddr_len);
 93                         printf("connect sucessful\n");
 94                         for(i=0;i<FD_SETSIZE;i++){
 95         //将接收到对应的客户端的文件描述符放到自己定义的数组中,方便找到对应的文件描述符
 96                                 if(client[i]<0){
 97                                 client[i]=connfd;
 98                                 break;
 99                                 }
100                         }
101                         FD_SET(connfd,&oldset);//将connfd放到oldset保存
102                         if(connfd>nfds){        //因为nfds只存最大的文件描述符号
103                                 nfds=connfd;
104                         }
105                         if(i>maxno){//保证maxno是clinet[]最后一个元素的下标
106                                 maxno=i;
107                         }
108                         if(--requset==0){
109                                 continue;
110                         }
111                 }
112                 for(i=0;i<=maxno;i++){//检查client中文件描述符是否有发送数据
113                         if((sockfd=client[i])<0){
114                                 continue;
115                         }
116                         if(FD_ISSET(sockfd,&readset)){
117                                 if((n=read(sockfd,buf,sizeof(buf)))==0){
118                                         close(sockfd);
119                                         FD_CLR(sockfd,&oldset);//清除oldset中的connfd文件描述符
120                                 }else if(n>0){
121                                         printf("%s",buf);
122                                 }
123                 //如果后续没有需要执行操作的文件描述符,着退回select去,继续监听        
124                                 if(--request==0){
125                                         break;
126                                 }
127                         }
128                 }
129         }
130         close(listenfd);
131         return 0;
132 }

select特点:

  • 定义字符集,并设置监听的sock文件描述符,传入select()
  • 如果自定义的字符集没有接收到客户端的请求,则相应的fd位翻转为0
  • 因此用户需要在传入时,保存原先设置的文件描述集
  • 得到传出的集合,需要从文件描述集中自己判断,哪个满足条件
  • 因此用户需要再自定义对应的数组,自己进行判断

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下! Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明): int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 先说明两个结构体: 第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。 第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 具体解释select的参数: int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 返回值: 负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值