Chinaunix首页 | 论坛 | 博客

fx

  • 博客访问: 1372431
  • 博文数量: 115
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3964
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-02 14:36
文章分类
文章存档

2022年(2)

2019年(2)

2018年(10)

2017年(1)

2016年(50)

2015年(12)

2014年(9)

2013年(29)

分类: LINUX

2013-06-16 10:08:26

设想一个情景一个服务器在等待任意客户的到来,并接受客服传来的数据,然后对数据处理后再返还给客户。然后客户离开。服务器继续等待。
但是如果某一时刻服务器响应了一个客户A的服务请求,但是由于一些原因,该客户A确不能立刻将数据传递给服务器。于是导致服务器阻塞在等待
该客户的A数据到来。如果这个时候另一个客户B 携带着数据来请求响应。但是服务器正阻塞在等待客户A 的数据到来上,于是客户B 只能在队列中等待,
知道服务器处理完A 的请求。
当然这不是我们期望的,如果B 是携带着数据的,那么应该立刻响应B的请求。而不是阻塞在一个不知道什么时候才能传来数据的客户A上
就像排队买东西,如果轮到你时,突然你的手机响了。那么你需要接电话。但是后面的人不会一直等你打完电话,所以这个时候,你会站在旁边,而后面的
人会依次去买东西,等你打完电话。你就可以去买东西了。


下面的这个例子就是一个简单的 多客服-服务器 模型。两个客户分别传送一个数据给服务器,然后在接收服务器处理后的数据后便结束。服务器的设置,
只是简单的循环等待一个客服的链接请求,当一个客户发出请求后,服务器会便会响应,将传来的数据加一后,再会送给客户。直到这个客户的请求处理完了
服务器才会去等待下一个请求,如果在服务器处理一个客户的请求过程中别的客户也发出了请求,那么就会在队列中等待。


下面是服务器的 代码 server1.c
int main(void){
        int socketfd;
        if((socketfd=socket(AF_INET,SOCK_STREAM,0))==-1){
                perror("create a new socket error");
                exit(1);
        }   
     
        struct sockaddr_in server_address;
        server_address.sin_family=AF_INET;
        server_address.sin_port=htons(8000);     //随便选的大于1024的端口号
        if(inet_pton(AF_INET,"127.0.0.1",&server_address.sin_addr.s_addr)==-1){   //使用本地回路
                perror("inet_pton error");
                exit(1);
        }   


        if(bind(socketfd,(struct sockaddr *)&server_address,
        sizeof(server_address))==-1){ //把套接字绑定到上面设置的地址
                perror("bind error");
                exit(1);
        }    
        if(listen(socketfd,5)==-1){
                perror("listen error");
                exit(1);
        }   


        int client;
        int data;
        while(1){ //循环等待客户的请求
                if((client=accept(socketfd,NULL,NULL))==-1){
                        perror("accepr error");
                        exit(1);
                }   
                read(client,&data,1); //当有一个请求后,读客户发送的数据
                data++;
                write(client,&data,1);       //处理后返回给客户
        }
        exit(0);
}


然后是两个客户1的代码。
client1.c


int main(void){
        int server_fd;
        server_fd=socket(AF_INET,SOCK_STREAM,0);
        if(server_fd==-1){
                perror("create a new socket error");
                exit(1);
        }   

//指定要链接的服务器的地址
        struct sockaddr_in server_address;
        server_address.sin_family=AF_INET;
        server_address.sin_port=htons(8000);    
        if(inet_pton(AF_INET,"127.0.0.1",&server_address.sin_addr.s_addr)==-1){      //将字符串转化为ip地址格式
                perror("inet_pton error error");
                exit(1);
        }   

        if(connect(server_fd,(struct sockaddr *)&server_address,
                sizeof(server_address))==-1){ //链接请求
                        perror("connect error");
                        exit(1);
                }   
        int data='a';
        sleep(5);
printf("after 5 secondes\n");    //服务器响应后,休眠五秒后,该客户才发送数据
        write(server_fd,&data,1);
        printf("\nclient 1 send data(%c) to server\n",data);
        read(server_fd,&data,1);
        printf("client 1 read data(%c) from server\n",data);
        close(server_fd);
        exit(0);
}

客户2 的代码
client2.c
int main(void){
        int server_fd;
        server_fd=socket(AF_INET,SOCK_STREAM,0);
        if(server_fd==-1){
                perror("create a new socket error");
                exit(1);
        }   

        struct sockaddr_in server_address;
        server_address.sin_family=AF_INET;
        server_address.sin_port=htons(8000);
        if(inet_pton(AF_INET,"127.0.0.1",&server_address.sin_addr.s_addr)==-1){
                perror("inet_pton error error");
                exit(1);
        }   
        if(connect(server_fd,(struct sockaddr *)&server_address,
                sizeof(server_address))==-1){
                        perror("connect error");
                        exit(1);
                }   
        int data='A';
        write(server_fd,&data,1);
        printf("\nclient 2 send data(%c) to server\n",data);
        read(server_fd,&data,1);
        printf("client2 read data(%c) from server\n",data);
        close(server_fd);
        exit(0);
}

如上面注释的。客户1 运行后会向服务器请求响应。服务器响应该请求后,客户1 却在五秒后才发送数据。
     客服2 运行后向服务器请求响应。 但是客户2 会立刻发送数据。

下面让两个客户以不用是顺序运行,来看下输出结果。先让客户2 先运行,而客户1后运行
feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./client2 & ./client1 &
[2] 4629
[3] 4630
feng@ubuntu:~/learn_linux_c_second/chapter_14$ 
client 2 send data(A) to server
client2 read data(B) from server
after 5 secondes

client 1 send data(a) to server
client 1 read data(b) from server

客户2运行后立刻得到响应并获得处理后的数据。客户1得到响应五秒后才会发送数据然后获得处理后的数据。这里并没有什么问题

现在让客户1 先运行,客户2后运行。再来看看输出情况
feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./client1 & ./client2 &
[2] 4798
[3] 4799
feng@ubuntu:~/learn_linux_c_second/chapter_14$ 
client 2 send data(A) to server
after 5 secondes
client2 read data(B) from server

client 1 send data(a) to server
client 1 read data(b) from server

这个时候问题出现了。客户1 先运行,所以他先得到服务器的响应。但是五秒后才会发送数据给服务器。所以服务器就会阻塞在等待客户1的数据到来上
而此时,客户2 却已经发送数据给服务器了。但是正如输出所示,客户2确不得不等到 五秒后 才能得到服务器的请求。

这就是我们需要解决的问题。我们需要让服务器应该立刻服务携带数据的客户2 而不是阻塞在客户1中使 客户2 即使有数据也无法得到处理。
I/O多路转换提供了一种解决这种问题的方法。
#include
int select(int maxfd,fd_set *restrict readfds,
  fd_set *restrict writefds,fd_set *restrict exceptfds,
  struct timeval *restrict tvptr);

maxfd参数可以为FD_SETSIZE,他说明了最大的描述符。
select调用会在 0到maxfdp-1 范围内的文件描述符中测试,readfds文件描述符集中设置的位中是否有可读的,writefds文件描述符集中设置的位中是否有
可写的,exceptfds文件描述符集中设置的位中是否有异常状态。(如果某个描述符集为NULL,表示不关心该描述符集) 

tvptr指示了select调用愿意等待的时间。为NULL是表示永远等待。为零表示不等待。

select成功返回时,会返回准备好的描述符数,并且在各个描述符集中设置准备好的描述符所在的位。
可以用FD_ISSET(int  fd, fd_set *fdset) 在select返回后测试某个描述符在 某个描述符集(读集,写集,异常集)中是否被置位,被置位则说明该描述符
现在可以非阻塞的可读或可写或异常(根据所在描述符集而定)。

void FD_SET(int fd,fd_set *fdset); 设置一个描述符集中关心的描述符。select调用时会检查该位是否可读或可写或有异常
void FD_CLR(int fd,fd_set *fdset); 清除一个描述符集中的某个位。select调用时不会检查该位是否可读或可写或有异常
void FD_ZERO(fd_set *fdset);  初始化一个描述符集,将该描述符集中的所有位置零

那么select函数就提供了一个解决上面的多客服—服务器的问题。我们可以把 服务器套接字添加到 测试是否可读的描述符集中。然后调用select等待可读描述符集中是否有描述符可读。


这时如果一个客户发送请求,会导致服务器的套接可读,然后select调用返回,然后使用FD_ISSET测试如果真是服务器套接字在可读描述符中被设置,
那么就调用 accept获得 为客户服务的套接字,但是我们并不是使用该套接字去读客户的数据,而是将它添加到 测试可读描述集中。
然后回到select中继续等待可读描述符集中是否有数据可读。
如果这时候select调用返回,那么再测试是否是服务器套接字可读,如果是那说明又一个客服发送了请求,那么依旧向上面说的一样处理。
否则,说明之前添加的 一个为客户服务的套接字 可读,也就是说客户发送数据过来了。这个时候我们再调用read去读数据,那么read绝不会再阻塞。

下面是修改后的服务器代码 server1.c
int main(void){
        int server_fd;
        if((server_fd=socket(AF_INET,SOCK_STREAM,0))==-1){
                perror("create a new socket error");
                exit(1);
        }   
     //设置服务器地址和端口
        struct sockaddr_in server_address;
        server_address.sin_family=AF_INET;
        server_address.sin_port=htons(8000);
        if(inet_pton(AF_INET,"127.0.0.1",&server_address.sin_addr.s_addr)==-1){
                perror("inet_pton error");
                exit(1);
        }   

        if(bind(server_fd,(struct sockaddr *)&server_address,
        sizeof(server_address))==-1){                       //绑定套接字到设置的服务器地址上
                perror("bind error");
                exit(1);
        }    
        if(listen(server_fd,5)==-1){     //创建监听队列
                perror("listen error");
                exit(1);
        }   
    //设置  可读描述符集,并添加服务器套接字     
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(server_fd,&readfds);
     
        fd_set testfds;
        int client;
        int i;
        int nreads;
        while(1){
                testfds=readfds;     //使用临时变量,防止后面添加的  为客户服务的套接字对本次测试产生影响
                if(select(FD_SETSIZE,&testfds,(fd_set*)0,(fd_set*)0,(struct timeval *)0)==-1){
                        perror("select error");
                        exit(1);
                }
                for(i=0;i
                        if(FD_ISSET(i,&testfds)){
                                if(i==server_fd){              //如果该描述符可读并且是 服务器套接字那么说明一个 客户请求到来
                                        client=accept(server_fd,NULL,NULL);   //获得  为客服服务的套接字
                                        if(client==-1){
                                                perror("accept error");
                                                exit(1);
                                        }
                                        FD_SET(client,&readfds);       //不去读客户数据而是把 为客户服务的套接字添加到 可读描述符集中
                                /*
                                    如果不是服务器套接字可读,那么就是之前添加的 为客户服务的套接字可读,
                                    也就是说有客户发送数据过来
                                */
}else{
                                        ioctl(i,FIONREAD,&nreads);  
                                        if(nreads==0){ //发现 读数据返回0 表示客户离开
                                                close(i);
                                                FD_CLR(i,&readfds);     //将该描述符移除 测试可读描述符集 ,应为该客户已经离开了
                                        }else{ //如果数据不为0,说明是真是数据,那么就处理数据。
                                                int data;
                                                read(i,&data,1);
                                                data++;
                                                write(i,&data,1);
                                        }


                                }
                        }




                }




        }
        exit(0);
}


现在再来看看 客户1 和客户2 不同的运行顺序产生的结果
feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./client2 & ./client1 &
[2] 5230
[3] 5231
feng@ubuntu:~/learn_linux_c_second/chapter_14$ 
client 2 send data(A) to server
client2 read data(B) from server
after 5 secondes

client 1 send data(a) to server
client 1 read data(b) from server

[2]-  Done                    ./client2
[3]+  Done                    ./client1
feng@ubuntu:~/learn_linux_c_second/chapter_14$ ./client1 & ./client2 &
[2] 5233
[3] 5234
feng@ubuntu:~/learn_linux_c_second/chapter_14$ 
client 2 send data(A) to server
client2 read data(B) from server
after 5 secondes

client 1 send data(a) to server
client 1 read data(b) from server

[2]-  Done                    ./client1
[3]+  Done                    ./client2


现在无论是 哪一个客户先运行。服务器不会阻塞在一个客户上,而另一个客户有数据却得不到处理。因为使用了select后,服务器不在关心是谁在请求,
他只是响应请求,然后把 与客户联系的套接字添加到 测试可读描述符上。然后继续等待 测试可读描述符集 中的一个描述符可读,如果是与客户联系的
描述符可读,那么说明又数据,那么服务器就能立刻得到数据,而不会有任何阻塞
所以无论客户如何到达,如何发送数据。服务器都不理会。他只关系,是否有请求,是否有数据,有就做相应处理。所以无论如何,一个客户绝不会应为另一个
客户的数据还未发送而导致自己的数据得不到处理。

最后需要注意的是:我们在上面使用了一个临时中间变量 testfds来测试可读描述符集中 是否有可读描述符。然后在循环中也是用 testfds 来测试
应为,如果直接使用readfds 会使本次的 添加与服务器联系的套接字操作对 后面的测试产生影响。
因为   FD_SET(client,&readfds); 把 与客户联系的套接字 添加到 测试可读描述符集中后,如果使用readfds 来进行后续的测试,服务器会以为被设置的
与 服务器联系的套接字 是由select 调用返回设置的,服务器便会认为有数据可读,而 实际上 可能根本无数据传送过来。
阅读(2534) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~