系统相关
首页 > 系统相关> > linux网络编程-IO复用——select

linux网络编程-IO复用——select

作者:互联网

本篇回答以下两个问题:

1、什么是IO复用?

2、什么是select?


1、什么是IO复用?

IO(read-in and write-out)指的是文件描述符的读写。

从文件描述符读写数据可以有多种实现方式,包括:非阻塞模型、非阻塞模型、IO复用、信号驱动、异步IO。

针对一个文件描述符读写时采用阻塞模型和非阻塞模型,则需要单独占用一个线程或者进程,而多个文件描述符的读写,就需要使用多线程或多进程分别针对每个文件描述符来处理。

IO复用就是要为了在一个线程或者进程中,能够把多个文件描述符的读写时间进行监控起来,达到单线程或单进程进行多个文件描述符进行读写的能力。

IO复用的实现方式也有多种:select、poll、epoll等。

2、什么是select?

函数原型为

1 #include <sys/select.h>
2 #include <sys/time.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5 int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数说明:

maxfdp:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;

readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。

timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间

timeval结构体定义如下:

1 struct timeval
2 {      
3     long tv_sec;   /*秒 */
4     long tv_usec;  /*微秒 */   
5 };

select使用范例:
当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:

1 fd_set rset;   
2 int fd;   
3 FD_ZERO(&rset);   
4 FD_SET(fd, &rset);   
5 FD_SET(stdin, &rset);

然后调用select函数,拥塞等待文件描述符事件的到来;如果超过设定的时间,则不再等待,继续往下执行(这里传入NULL,选择一直等待)。

1 select(fd+1, &rset, NULL, NULL,NULL);

select返回后,用FD_ISSET测试给定位是否置位:

1 if(FD_ISSET(fd, &rset)   
2 { 
3     ... 
4     //do something  
5 }

例子1:监控标准输入(fd=0)端口是否可读

 1 #include <sys/select.h>
 2 #include <sys/time.h>
 3 #include <sys/types.h>
 4 #include <unistd.h>
 5 #include <stdio.h>
 6 
 7 int main()
 8 {
 9     fd_set rd;
10     struct timeval tv;
11     int err;
12     
13 
14     FD_ZERO(&rd);
15     FD_SET(0,&rd);
16     
17     tv.tv_sec = 5;
18     tv.tv_usec = 0;
19     err = select(1,&rd,NULL,NULL,&tv);
20     
21     if(err == 0) //超时
22     {
23         printf("select time out!\n");
24     }
25     else if(err == -1)  //失败
26     {
27         printf("fail to select!\n");
28     }
29     else  //成功
30     {
31         printf("data is available!\n");
32     }
33 
34     
35     return 0;
36 }

例子2:TCP服务端监控多个客户端

服务端代码:

 1 #include <sys/types.h> 
 2 #include <sys/socket.h> 
 3 #include <stdio.h> 
 4 #include <netinet/in.h> 
 5 #include <sys/time.h> 
 6 #include <sys/ioctl.h> 
 7 #include <unistd.h> 
 8 #include <stdlib.h>
 9 
10 int main() 
11 { 
12     int server_sockfd, client_sockfd; 
13     int server_len, client_len; 
14     struct sockaddr_in server_address; 
15     struct sockaddr_in client_address; 
16     int result; 
17     fd_set readfds, testfds; 
18     server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket 
19     server_address.sin_family = AF_INET; 
20     server_address.sin_addr.s_addr = htonl(INADDR_ANY); 
21     server_address.sin_port = htons(8888); 
22     server_len = sizeof(server_address); 
23     bind(server_sockfd, (struct sockaddr *)&server_address, server_len); 
24     listen(server_sockfd, 5); //监听队列最多容纳5个 
25     FD_ZERO(&readfds); 
26     FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
27     while(1) 
28     {
29         char ch; 
30         int fd; 
31         int nread; 
32         testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
33         printf("server waiting\n"); 
34 
35         /*无限期阻塞,并测试文件描述符变动 */
36         result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
37         if(result < 1) 
38         { 
39             perror("server5"); 
40             exit(1); 
41         } 
42 
43         /*扫描所有的文件描述符*/
44         for(fd = 0; fd < FD_SETSIZE; fd++) 
45         {
46             /*找到相关文件描述符*/
47             if(FD_ISSET(fd,&testfds)) 
48             { 
49               /*判断是否为服务器套接字,是则表示为客户请求连接。*/
50                 if(fd == server_sockfd) 
51                 { 
52                     client_len = sizeof(client_address); 
53                     client_sockfd = accept(server_sockfd, 
54                     (struct sockaddr *)&client_address, &client_len); 
55                     FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
56                     printf("adding client on fd %d\n", client_sockfd); 
57                 } 
58                 /*客户端socket中有数据请求时*/
59                 else 
60                 { 
61                     ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
62                     
63                     /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
64                     if(nread == 0) 
65                     { 
66                         close(fd); 
67                         FD_CLR(fd, &readfds); //去掉关闭的fd
68                         printf("removing client on fd %d\n", fd); 
69                     } 
70                     /*处理客户数据请求*/
71                     else 
72                     { 
73                         read(fd, &ch, 1); 
74                         sleep(5); 
75                         printf("serving client on fd %d\n", fd); 
76                         ch++; 
77                         write(fd, &ch, 1); 
78                     } 
79                 } 
80             } 
81         } 
82     } 
83 
84     return 0;
85 }

客户端代码

 1 //客户端
 2 #include <sys/types.h> 
 3 #include <sys/socket.h> 
 4 #include <stdio.h> 
 5 #include <netinet/in.h> 
 6 #include <arpa/inet.h> 
 7 #include <unistd.h> 
 8 #include <stdlib.h>
 9 #include <sys/time.h>
10 
11 int main() 
12 { 
13     int client_sockfd; 
14     int len; 
15     struct sockaddr_in address;//服务器端网络地址结构体 
16      int result; 
17     char ch = 'A'; 
18     client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
19     address.sin_family = AF_INET; 
20     address.sin_addr.s_addr = inet_addr("127.0.0.1");
21     address.sin_port = htons(8888); 
22     len = sizeof(address); 
23     result = connect(client_sockfd, (struct sockaddr *)&address, len); 
24     if(result == -1) 
25     { 
26          perror("oops: client2"); 
27          exit(1); 
28     } 
29     //第一次读写
30     write(client_sockfd, &ch, 1); 
31     read(client_sockfd, &ch, 1); 
32     printf("the first time: char from server = %c\n", ch); 
33     sleep(5);
34     
35     //第二次读写
36     write(client_sockfd, &ch, 1); 
37     read(client_sockfd, &ch, 1); 
38     printf("the second time: char from server = %c\n", ch);
39     
40     close(client_sockfd); 
41    
42     return 0; 
43 }

 

深入理解select模型:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

基于上面的讨论,可以轻松得出select模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

标签:set,描述符,fd,IO,linux,FD,include,select
来源: https://www.cnblogs.com/zzx2bky/p/16341222.html