select模型是socket中的一种多路IO复用模型之一,通过轮询的方式来完成多路访问控制。
一个很简单的例子来描述select模型:
幼儿园老师要照顾所有的小朋友,每天他都会轮流去问小朋友:“小朋友小朋友,你饿了吗?”
如果小朋友饿了,那么老师就给这个小朋友喂饭,否则就开始询问下一个朋友,一直循环下去直到放学。
同时,如果班级里有其他的同学来了,也把他加到询问队列。如果有哪个同学生病了,则把它踢出询问队列。
select模型的原理就是这样,把所有连接的客户端socket加入到一个集合中去,然后一直不断轮询,判断哪一个socket有数据到达,就读取数据。否则继续轮询下一个数据。
linux系统在编译的时候就固定了select模型文件描述符集合的大小为1024个,这个大小无法更改,因此,select模型只适用于并发量小于1024个的服务连接。
1 2 |
> grep "FD_SETSIZE" /usr/include/ -R /usr/include/linux/posix_types.h:#define __FD_SETSIZE 1024 |
一、相关函数
1 2 3 4 5 6 |
#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
select用来判断文件描述符结合中是否有文件描述符发生了相应的事件,如果有事件则返回(这个策略可以改变)。
参数说明:
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态。
readfds:监控有读数据到达文件描述符集合,传入传出参数。
writefds:监控写数据到达文件描述符集合,传入传出参数。
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数。
timeout:定时阻塞监控时间,3种情况:
1.NULL,永远等下去;2.设置timeval,等待固定时间;3.设置timeval里时间均为0,检查描述字后立即返回
。返回值: 文件描述符集合中有响应时间的文件描述符个数。
超时类型的结构体:
1 2 3 4 |
struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; |
对监控描述符集合的操作:
1 2 3 4 |
void FD_CLR(int fd, fd_set *set); // 把文件描述符集合里的fd设置0 int FD_ISSET(int fd, fd_set *set); // 测试文件描述符集合里fd是否置1 void FD_SET(int fd, fd_set *set); // 把文件描述符集合里fd位置1 void FD_ZERO(fd_set *set); // 把文件描述符集合里所有位清0 |
通常情况下,我们需要一个客户端数组保存每一个客户端的状态,表示当前集合中的该元素是否已经有存在或者连接。然后还要有一个最大的文件描述符标志,用来轮询。
一旦select返回,我们要先判断是否为服务端的socket,如果是服务端的,说明有新的连接加入,把这个新连接加入到集合中去。否则则说明是客户端连接有响应,这时候需要一个一个去循环判断,看哪个socket有数据到达,然后处理。
二、示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
#include<stdio.h> #include<ctype.h> #include<sys/select.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<arpa/inet.h> #include<stdlib.h> #include<string.h> const int MAX_CONN_NUM = 1024; const int MAX_BUFF_SIZE = 1024; const int MAX_IP_LENGTH = 16; int main(int argc, char** argv){ if (argc != 2){ printf("Usage: ./server port\n"); return 0; } fd_set t_set, all_set; int serv_fd, clnt_fd, max_index, max_fd, port; struct sockaddr_in serv_addr, clnt_addr; socklen_t addr_len = sizeof(clnt_addr); char ip[MAX_IP_LENGTH], buff[MAX_BUFF_SIZE]; int ret, i, n, client[FD_SETSIZE]; // FD_SETSIZE = 1024 serv_fd = socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(atoi(argv[1])); if (-1 == bind(serv_fd, (struct sockaddr*)&serv_addr, addr_len)){ perror("bind error"); return 0; } if (-1 == listen(serv_fd, MAX_CONN_NUM)){ perror("listen error"); return 0; } printf("server start and listen %s\n", argv[1]); // 初始化客户端文件描述符 for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; // 初始化数组下标和最大文件描述符 max_index = -1; max_fd = serv_fd; // 把监控文件符清零 FD_ZERO(&all_set); // 添加服务端socket到fd_sets FD_SET(serv_fd, &all_set); while(1){ t_set = all_set; ret = select(max_fd + 1, &t_set, 0, 0, 0); if (ret == -1){ perror("select error"); break; } if (FD_ISSET(serv_fd, &t_set)){ // 服务端socket响应,表示有新连接 clnt_fd = accept(serv_fd, (struct sockaddr*)&clnt_addr, &addr_len); if (-1 == clnt_fd){ perror("accept error"); break; } // 判断连接数量是否超过限制 for (i = 0; i < FD_SETSIZE; i++){ if (client[i] < 0){ client[i] = clnt_fd; break; } } if (i == FD_SETSIZE){ printf("too many connetions\n"); close(clnt_fd); continue; } inet_ntop(AF_INET, &clnt_addr.sin_addr, ip, sizeof(clnt_addr)); port = clnt_addr.sin_port; printf("connect to [%s:%d]\n", ip, port); // 添加新的客户端到集合 if (i > max_index) max_index = i; if (clnt_fd > max_fd) max_fd = clnt_fd; FD_SET(clnt_fd, &all_set); // 如果有响应的描述符只有一个,开始下一次select if (--ret == 0) continue; } for (i = 0; i <= max_index; i++){ // 轮询所有的socket if ((clnt_fd = client[i]) < 0){ continue; } // 如果有数据,正常接受字符 if (FD_ISSET(clnt_fd, &t_set)){ bzero(buff, MAX_BUFF_SIZE); n = read(clnt_fd, buff, MAX_BUFF_SIZE); if (-1 == n){ perror("read error"); continue; }else if (0 == n){ FD_CLR(clnt_fd, &all_set); client[i] = -1; printf("closed!\n"); sleep(1); close(clnt_fd); continue; } printf("recv %d data: %s\n", n, buff); for(int j = 0; j < n; j++){ buff[j] = toupper(buff[j]); } n = write(clnt_fd, buff, n); if (-1 == n){ perror("write error"); continue; } } } } close(serv_fd); return 0; } |
评论