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