我们所使用的I/O模型一共有五种。
分别为阻塞I/O,非阻塞I/O,I/O复用,信号驱动I/O,异步I/O。
所谓I/O复用就是指管理多个I/O文件描述符,一般会使用(select,poll,epoll)3个函数中的一个来实现I/O复用。
下面就介绍一下其中的select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select的5个参数:
第一个参数是当前管理的所有文件描述符的最大值加1
因为在select中采用的是轮询的机制,输入参数是不包含的。
你可以这样理解: for(fd = 0; fd<maxfd+1 ; fd++)
[顺带一提: 文件描述符0是stdin,1是stdout,2是stderr]
第二个参数是读集合
你要管理哪些文件描述符的读的事件,就把它放到fd_set *readfds结构体中(用FD_SET()函数);
第三个参数是写集合
同样,要管理哪些文件描述符的写的事件,就把它放到fd_set *readfds结构体中(用FD_SET()函数);
第四个参数是异常集合
就是监视异常事件的发生
第五个参数是超时时间的设置
struct timeval {
long tv_sec; 秒
long tv_usec; 毫毛
}
不要忽略这也是一个输入输出参数(即该参数会被函数改变),如果是一个循环,那么每次都要重新赋值该参数。
注意:select中除了第一个参数都是输入输出参数,即放在这几个位置的值都会被改变
读,写,异常集合输入参数是要监视的集合,使用完select后会变成有事件发生的事件集合,而timeout会被select函数减少
第1个参数必须设置正确(在window系统中可以不设置,但在linux系统中必须设置)
第2,3,4个参数若设置为NULL,表示不关心任何事件,若不关心其中的一种事件,可将其设置为NULL。
顺带提一下[int fileno(FILE *stream);],这个函数可以用来取得参数stream指定的文件流所使用的文件描述符。
用select函数实现并发服务器并发数量受两方面限制
1.最大文件描述符(系统参数)
可在LINUX终端直接输入 ulimit -n 查看,用 ulimit -n [数值] 改变参数
或者在程序中用setrlimit 函数改变(注意:setrlimit 函数只能改变当前进程的最大文件描述符)
2.select中的fd_set集合容量FD_SETSIZE,不容易改变,需要重新编译内核
下面是select实现的(可实现多个客户端连接)回射服务器代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/socket.h> 4 #include <string.h> 5 #include <sys/types.h> 6 #include <unistd.h> 7 #include <errno.h> 8 #include <arpa/inet.h> 9 #include <netinet/in.h> 10 #include <sys/select.h> 11 #include <sys/time.h> 12 #include <sys/types.h> 13 14 15 void myerr(char *m) 16 { 17 perror(m); 18 exit(0); 19 } 20 21 int count = 0; 22 int main() 23 { 24 int listenfd; 25 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 26 myerr("socket error"); 27 28 int on = 1; 29 if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 30 myerr("setsockopt error"); 31 32 33 struct sockaddr_in servaddr; 34 memset(&servaddr, 0, sizeof(servaddr)); 35 servaddr.sin_port = htons(5188); 36 servaddr.sin_family = AF_INET; 37 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 38 39 if(bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) 40 myerr("bind error"); 41 42 if(listen(listenfd, SOMAXCONN) < 0) 43 myerr("listen error"); 44 45 int i, maxi=0, conn, ret, maxfd, nready; 46 char recvbuf[1024] = ""; 47 struct sockaddr_in peeraddr; 48 int connfd[FD_SETSIZE]; 49 for(i=0; i<FD_SETSIZE; i++) 50 connfd[i] = -1; 51 socklen_t peerlen = sizeof(peeraddr); 52 53 maxfd = listenfd; 54 fd_set allset, rset; 55 FD_ZERO(&allset); 56 FD_ZERO(&rset); 57 FD_SET(listenfd, &allset); 58 59 for(;;) 60 { 61 rset = allset; 62 sleep(1); 63 nready = select(maxfd+1, &rset, NULL, NULL, NULL); 64 if(nready == -1) 65 { 66 if(errno == EINTR) 67 continue; 68 myerr("select"); 69 } 70 if(FD_ISSET(listenfd, &rset)) 71 { 72 if((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0) 73 myerr("accept error"); 74 for(i=0; i<FD_SETSIZE; i++) 75 { 76 if(connfd[i] == -1) 77 { 78 connfd[i] = conn; 79 if(i > maxi) 80 maxi = i; 81 break; 82 } 83 } 84 // count++; 85 // printf("%d ", count); 86 if(i == FD_SETSIZE) 87 { 88 fprintf(stderr, "too many clients "); 89 exit(1); 90 } 91 printf("ip: %s port: %d ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); 92 FD_SET(conn, &allset); 93 if(conn > maxfd) 94 maxfd = conn; 95 if(--nready <= 0) 96 continue; 97 } 98 for(i=0; i<=maxi; i++) 99 { 100 if(connfd[i] == -1) 101 continue; 102 conn = connfd[i]; 103 104 if(FD_ISSET(conn, &rset)) 105 { 106 memset(recvbuf, 0, sizeof(recvbuf)); 107 ret = read(conn, recvbuf, sizeof(recvbuf)); 108 if(ret == 0) 109 { 110 printf("client close "); 111 close(conn); 112 connfd[i] = -1; 113 while(connfd[maxi] == -1) 114 { 115 maxi --; 116 if(maxi == 0) 117 break; 118 } 119 FD_CLR(conn, &allset); 120 continue; 121 } 122 write(conn, recvbuf, ret); 123 fputs(recvbuf, stdout); 124 if(--nready <= 0) 125 break; 126 } 127 } 128 } 129 close(listenfd); 130 return 0; 131 }