标签:set 描述符 fd IO linux FD include 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
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。
