目录
select的多路复用实现网络socket的多并发服务器的流程图
-
select函数简介
select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时 间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。
#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);
void FD_CLR(int fd, fd_set *set); //用来删除一个已经没有使用的文件描述符fd
int FD_ISSET(int fd, fd_set *set); //判断文件描述是否在集合中
void FD_SET(int fd, fd_set *set); //将文件描述符fd加入的select的文件描述符集合
void FD_ZERO(fd_set *set); //将select的文件描述符集合清空
/*可以用来设置select的超时时间*/
struct timeval {
long tv_sec; //秒
long tv_usec; //毫秒
};
-
参数说明
-
select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是readfds(文件描述符有数据到来可读)、 writefds(文件描述符可写)、和exceptfds(文件描述符异常)。如果我们只是监视其中的一个或两个的文件描述符的话,其他不足要的可以将其值置为NULL。
-
调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、 或者有错误异常),或者超时( timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到 究竟是哪些文件描述符就绪。
-
select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-1;
-
第一个参数max_fd指待测试的fd的总个数,它的值是待测试的最大文件描述符加1。Linux内核从0开始到max_fd-1扫描文件描述 符,如果有数据出现事件(读、写、异常)将会返回;假设需要监测的文件描述符是8,9,10,那么Linux内核实际也要监测0~7,此时真 正带测试的文件描述符是0~10总共11个,即max(8,9,10)+1,所以第一个参数是所有要监听的文件描述符中最大的+1。
-
中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为 NULL;
-
最后一个参数是设置select的超时时间,如果设置为NULL则永不超时;
-
需要注意的是待测试的描述集总是从0, 1, 2, ...开始的。 所以, 假如你要检测的描述符为8, 9, 10, 那么系统实际也要 监测0, 1, 2, 3, 4, 5, 6, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8, 9, 10) + 1
-
在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数中,这也意味着select所用到的FD_SET是有限的,也正是这个原因select()默认只能同时处理1024个客户端的连接请求: /linux/posix_types.h: #define __FD_SETSIZE 1024
-
select的不足之处
-
每次有事件到来他都需要遍历整个文件描述符的集合,不能精准的处理;比如:集合中有0,1,2,3 ..... 30多个文件描述符时,如果第29个文件描述符有事件到来,那么他就要遍历前0...29 + 1;这样的遍历对我们来说几乎没用多长的时间,但是对内核和CPU来说已经很间了。如果有更多的文描述符呢?但是最多不会超过1024个;
-
最大连接数的限制,如果前1024个文件描述符均已连接成功,但是再有客户来连接的话就会连接不上。试想一下,大多数服务器如果同时只能连接1024个客户端的话,那么12306的购票和淘宝的双十一还有那么有活力吗?一次就允许1024客户访问,这还不把我们等的着急疯了。虽然可以通过setrlimit()、修改宏定义甚至重 新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;
-
每次调用 select()都需要把fd集合从用户态拷贝到内核态,之后内核需要遍历所有传递进来的fd,这时如果客户端fd很多 时会导致系统开销很大。
-
select的多路复用实现网络socket的多并发服务器的流程图
-
服务器实现代码
-
头文件
#ifndef __SOCKET_SELECT_SERVER_H__ #define __SOCKET_SELECT_SERVER_H__ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <time.h> #include <pthread.h> #include <getopt.h> #include <libgen.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define BUF_SIZE 1024 int get_opt(int argc, char * const argv[],const char *optstring); //Linux下的参数解析函数 int socket_server_init(int listen_port, char *msg); //socket的封装函数 void select_start(int listenfd, char *msg); //select的封装函数 void print_usage(char *prograname) { printf("%s usage : \n", prograname); printf("-p(--port): specify sever listen port.\n"); printf("-m(--msg): specify sever write msg to client.\n"); printf("-d(--daemon): specify sever will go to run with daemon.\n"); printf("-h(--help): print this help information.\n"); return ; } #endif
-
源文件
#include "socket_select_server.h" int main(int argc, char *argv[]) { int listenfd, connfd; int ser_port; char *progname = NULL; int opt; fd_set rdset; int rv; int i, j; int found; int maxfd = 0; char buf[BUF_SIZE]; int fds_arr[1024]; get_opt(argc, argv,"p:dm:h"); //Linux下的参数解析函数 return 0; } int get_opt(int argc, char * const argv[],const char *optstring) { int port = 0; int ch; char *msg = NULL; struct option opts[] = { {"port", required_argument, NULL, 'p'}, {"write_msg", required_argument, NULL, 'm'}, {"daemon", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; while((ch=getopt_long(argc, argv, "p:m:dh", opts, NULL)) != -1 ) { switch(ch) { case 'p': port=atoi(optarg); break; case 'm': msg = optarg; break; case 'd': daemon(0,0); break; case 'h': print_usage(argv[0]); return 0; } } if( !port||!msg) { print_usage(argv[0]); return 0; } socket_server_init(NULL,port, msg); //socket的封装函数 } int socket_server_init(char * ip,int listen_port, char *msg) { int lisfd = 0; int clifd = 0; int on = 1; int rv = 0; pid_t pid; char buf[BUF_SIZE]; struct sockaddr_in serv_addr, cli_addr; socklen_t len = sizeof(serv_addr); if ((lisfd = socket(AF_INET,SOCK_STREAM, 0))< 0) //服务器第一步,socket(); { printf("Socket error:%s\n", strerror(errno)); return -1; } printf("socket[%d] successfuly!\n", lisfd); setsockopt(lisfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //端口短时间内复用 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //可以接受任意ip的客户端访问,并且将本地字节序转化为网络字节序 serv_addr.sin_port = htons(listen_port); //并且将本地字节序转化为网络字节序 if ((rv = bind(lisfd, (struct sockaddr *)&serv_addr, len)) < 0) //服务器第二部bind; { printf("Bind error %s\n", strerror(errno)); goto EXIT; } if ((rv = listen(lisfd, 13)) < 0) //服务器第三步,listen { printf("Listen error:%s\n", strerror(errno)); goto EXIT; } select_start(lisfd, msg); //select的封装函数 return clifd; EXIT: close(lisfd ); close(clifd ); return -1; } void select_start(int listenfd, char *msg) { int maxfd = 0; char buf[BUF_SIZE]; int fds_array[1024]; int i, j; int rv ; int found; int connfd; fd_set rdset; /* 初始化fds_arr */ for (i = 0; i < ARRAY_SIZE(fds_arr); ++i) { fds_array[i] = -1; } fds_arr[0] = listenfd; //将listenfd传入到fds_arr[0] for ( ; ; ) { FD_ZERO(&rdset); //select 第一步,将reset置零 for ( i = 0; i<ARRAY_SIZE(fds_arr); i++) //遍历所有传入的fd { if (fds_arr[i] < 0) { continue; } maxfd = fds_arr[i] > maxfd ?fds_arr[i]:maxfd; //取得最大fd +1, FD_SET(fds_arr[i], &rdset); //将fds_arr[i]设置为refd; } rv = select(maxfd + 1, &rdset, NULL, NULL, NULL); //select开始 if (rv < 0) { printf("Select error:%s\n", strerror(errno)); break; } else if (rv == 0) //断开或连接超时 { printf("select get timeout.\n"); continue; } if (FD_ISSET(listenfd, &rdset)) //判断fd是否为所设置fd; { if ( ( connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0) //服务器第三步,accept { printf("Accept new client error: %s\n", strerror(errno)); continue; } found = 0; for (i = 0; i <ARRAY_SIZE(fds_arr); i++) //将clifd入数组 { if (fds_array[i] < 0) { printf("Accept new client[%d] and it into array.\n", connfd); fds_arr[i] = connfd; found = 1; break; } } if (!found) //已接受达到最大的文件描述符的数目,将其余的文件描述符关闭 { printf("Accept new client[%d] sueecssful but array is full, so refuse it.\n", connfd); close(connfd); } } else { for (i = 0; i<ARRAY_SIZE( fds_arr); i++) { if (fds_arr[i] < 0 || !FD_ISSET(fds_arr[i], &rdset)) //参数合法性判断,是否有描述符或描述符是否为所设置的 continue; if ((rv = read(fds_array[i], buf, BUF_SIZE)) <= 0) //服务器第四步read /write { printf("Socket[%d] read failure or get disconnected.\n", fds_array[i]); close(fds_arr[i]); fds_arr[i] = -1; } else { printf("socket[%d] read get %d bytes data\n", fds_arr[i], rv); if (write(fds_arr[i], msg, rv) < 0) { printf("socket[%d] write failure: %s\n", fds_arr[i], strerror(errno)); close(fds_array[i]); fds_arr[i] = -1; } } } } } }
-
运行结果
-
单个客户端连接
-
多客户端连接
- 多客户端并发连接服务器的服务器端
2.多客户端并发连接服务器的客户端端
3.客户端断开之后的服务器端
注:学识尚浅,如有不足地方敬请指出。谢谢!