I/O多路转接的基本思想:先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
注意:每一个路的IO操作,尽可能的时间短,不要有while循环。
POSIX.1标准定义了select函数,这可使得我们能够执行I/O多路转接。
#includeint 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数据类型可以进行的处理是:分配一个这种类型的变量;将这种类型的一个变量赋予同类型的另一个变量;或对这种类型的变量使用以下四个函数中的一个:
#includeint FD_ISSET(int fd, fd_set *set); 返回值:若fd在描述符集中则返回非零值,否则返回0void 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 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 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 } // connectfd109 } // fd_set110 } // for111 112 }//while113 114 115 close(sockfd);116 117 return 0;118 }
client.c
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define LEN 12812 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 }