Linux I/O多路转接之select函数
I/O多路转接的基本思想:先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
注意:每一个路的IO操作,尽可能的时间短,不要有while循环。
POSIX.1标准定义了select函数,这可使得我们能够执行I/O多路转接。
#include<sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 返回值:准备就绪的描述符数,若超时则返回0, 若出错则返回-1
select的第一个参数nfds的意思是“最大描述符加1”。在三个描述符集中找出最大描述符编号值,然后加1,这就是第一个参数。也可设置FD_SETSIZE,即常量1024,说明最大描述符数。
select的中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符说明了我们关心的可读、可写或处于异常条件的各个描述符。每个描述符集存放在一个fd_set数据类型中,这种数据类型为每一个可能的描述符保持一位。
对fd_set数据类型可以进行的处理是:分配一个这种类型的变量;将这种类型的一个变量赋予同类型的另一个变量;或对这种类型的变量使用以下四个函数中的一个:
#include<sys/select.h> int FD_ISSET(int fd, fd_set *set); 返回值:若fd在描述符集中则返回非零值,否则返回0 void FD_CLR(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
以上这些接口可实现为宏函数。调用FD_ZERO将一个指定的fd_set变量的所有位设置为0,即清空列表;调用FD_SET设置一个fd_set变量的指定位,即向表中添加一个文件描述符;调用FD_CLR则将一指定位清除,即从表中,移除一个文件描述符;最后,调用FD_ISSET测试一指定位是否设置,即判断文件描述符是否准备就绪。
声明了一个描述符集后,必须用FD_ZERO清除其所有位,然后设置各个位。
如:
1 fd_set rset; 2 int fd; 3 4 FD_ZERO(&rset); 5 FD_SET(fd, &rset); 6 FD_SET(STDIN_FILENO, &rset); 7 8 if(FD_ISSET(fd, &rset)){ 9 ……………… 10 }
当然,select的这中间三个参数中的一个或全部都可以是空指针,这表示相应状态并不关心。
select的最后一个参数timeout,表示愿意等待的时间:
1 struct timeval{ 2 long tv_sec; /*seconds*/ 3 long tv_usec;/*and microseconds*/ 4 };
有三种情况:
(1)timeout == NULL
永远等待。若捕捉到一个信号则中断此等待。当所等待的描述符中的一个已经准备好或捕捉到一个信号则返回。若捕捉到一个信号,则select返回-1,errno设置为EINTR。
(2)timeout->tv_sec == 0 && timeout->tv_usec == 0
完全不等待。测试所有指定的描述符并立即返回。
(3)timeout->tv_sec != 0 || timeout->tv_usec != 0
等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。若超时时还没有一个描述符准备好,则返回值为0.
对于select有三个可能的返回值
(1)返回值为1表示出错。如:所指定的描述符都没准备好就捕捉到一个信号。
(2)返回值0表示没有描述符准备好。若指定的描述符都没有准备好,而且指定的时间已经超时,则发生这种情况。此时所有描述符集皆被清零。
(3)正返回值表示已经准备好的描述符数,该数值是三个描述符集中已准备好的描述符数之和,所以若同一描述符已准备好读和写,那么再返回值中将其记为2.
示例程序:
server.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <unistd.h> 6 #include <fcntl.h> 7 #include <arpa/inet.h> 8 #include <netinet/in.h> 9 #include <sys/select.h> 10 #include <sys/time.h> 11 #include <string.h> 12 13 #define N 128 14 15 16 int main(int argc, const char *argv[]) 17 { 18 struct sockaddr_in serveraddr, clientaddr; 19 int sockfd; 20 int maxfd; 21 fd_set readfds, tmpfds; 22 int i = 0; 23 char buf[N] = {0}; 24 int connectfd; 25 socklen_t len = sizeof(struct sockaddr); 26 27 if(argc < 3) 28 { 29 fprintf(stderr,"Usage:%s serverip port.", argv[0]); 30 return -1; 31 } 32 33 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 34 { 35 perror("fail to socket"); 36 return -1; 37 } 38 39 serveraddr.sin_family = AF_INET; 40 serveraddr.sin_addr.s_addr = inet_addr(argv[1]); 41 serveraddr.sin_port = htons(atoi(argv[2])); 42 43 if(bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) 44 { 45 perror("fail to bind"); 46 return -1; 47 } 48 49 if(listen(sockfd, 10) < 0) 50 { 51 perror("fail to listen"); 52 return -1; 53 } 54 55 printf("sockfd = %d\n", sockfd); 56 57 maxfd = sockfd; 58 59 FD_ZERO(&readfds); 60 FD_SET(sockfd, &readfds); 61 62 while(1) 63 { 64 tmpfds = readfds; 65 if(select(maxfd+1, &tmpfds, NULL, NULL, NULL) < 0) 66 { 67 perror("fail to select"); 68 return -1; 69 } 70 71 for(i = 0; i < maxfd+1; i++) 72 { 73 if(FD_ISSET(i, &tmpfds)) 74 { 75 if(i == sockfd) // sockfd 76 { 77 if((connectfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len)) < 0) 78 { 79 perror("fail to accept"); 80 return -1; 81 } 82 printf("client:%s %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); 83 84 FD_SET(connectfd, &readfds); 85 maxfd = maxfd > connectfd ? maxfd:connectfd; 86 } 87 else // A B C 88 { 89 if(recv(i, buf, N, 0) < 0) 90 { 91 perror("fail to recv"); 92 return -1; 93 } 94 buf[strlen(buf) -1] = '\0'; 95 printf("client:%s ---> %s %d\n", buf, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); 96 if(strncmp(buf, "quit", 4) == 0) 97 { 98 FD_CLR(i, &readfds); 99 close(i); 100 continue; 101 } 102 strcat(buf, "++++++++++++"); 103 if(send(i, buf, N, 0) < 0) 104 { 105 perror("fail to send"); 106 return -1; 107 } 108 } // connectfd 109 } // fd_set 110 } // for 111 112 }//while 113 114 115 close(sockfd); 116 117 return 0; 118 }
client.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 #include <errno.h> 8 #include <string.h> 9 #include <unistd.h> 10 11 #define LEN 128 12 13 int main(int argc, const char *argv[]) 14 { 15 16 int sockfd; 17 struct sockaddr_in serveraddr; 18 char buf[LEN]; 19 20 if(argc != 3) 21 { 22 fprintf(stderr, "Usage:%s serverip port.\n", argv[0]); 23 return -1; 24 } 25 26 sockfd = socket(AF_INET, SOCK_STREAM, 0); 27 if(sockfd < 0) 28 { 29 perror("fail to socket"); 30 } 31 32 serveraddr.sin_family = AF_INET; 33 serveraddr.sin_addr.s_addr = inet_addr(argv[1]); 34 serveraddr.sin_port = htons(atoi(argv[2])); 35 36 if(connect(sockfd, (struct sockaddr*)&serveraddr,sizeof(struct sockaddr)) < 0) 37 { 38 perror("connect failed."); 39 return -1; 40 } 41 42 while(1) 43 { 44 45 printf("input > "); 46 fgets(buf, LEN, stdin); 47 if(send(sockfd, buf, sizeof(buf), 0) < 0) 48 { 49 perror("fail to send"); 50 return -1; 51 } 52 if(strncmp(buf,"quit", 4) == 0) 53 { 54 break; 55 } 56 if(recv(sockfd, buf, sizeof(buf), 0) < 0) 57 { 58 perror("fail to recv"); 59 return -1; 60 } 61 printf("buf:%s\n", buf); 62 } 63 64 close(sockfd); 65 66 67 return 0; 68 }