模拟实现select服务器
来源:互联网 发布:qq空间辅助软件 编辑:程序博客网 时间:2024/05/16 03:40
什么是I/O多路转接?
对于多个非阻塞I/O,怎么知道I/O何时已经处于可读或可写状态?
如果采用循环一直调用write/read,直到返回成功,这样的方式称为轮询(polling)。大多数时间I/O没有处于就绪状态,因此这样的轮询十分浪费CPU。
而一种比较好的技术是使用I/O多路转接,也叫做I/O多路复用。其基本思想为:先构造一个有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已经准备好可以进行I/O了。
接下来就是今天的重头戏select,曾有人认为select函数是I/O复用的全部内容。
select函数的功能
简单来说就是一个加强版的listen,select系统调用来让我们同时监视多个文件句柄的变化,然后程序会停在select这里等待,直到被监视的文件句柄有一个或者多个发生状态的变化。关于句柄就是文件描述符,其实也就是一个整数。
select函数的使用流程
我们下来就按这个流程使用select模拟实现服务器。
1.函数原型参数
nfds:是需要监视的最大文件描述符的值+1,也就是监视的文件描述符的数量
readfds:用户告诉内核,我关心哪些文件描述符上的读事件。
writefds:用户告诉内核,我关心哪些文件描述符上的写事件。
exceptfds : 用户告诉内核,我关心哪些文件描述符上的异常事件。
需要说明的是以上这三个参数都是作为输入型参数时的含义,作为输出型参数时表示我关心哪些已经就绪。
timeout : struct timeval 结构体类型。
timeout也就是设置等待时间为:
NULL:表示select没有timeout,select 将一直被阻塞,知道某个文件描述符发生变化。
0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在这个时间段内没有时间发生,select将超时返回。
2.返回值
成功:返回文件描述符状态已改变的个数。
为0:代表状态改变前已经超时,没有返回。
错误:返回-1,原因位于errno,参数readfds, writefds, exceptfds,timeout 则变成不可预测的随机值。
要理解select就要了解一个很重要的数据结构
fd_set
fd_set, select()机制中提供的一个数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
这个结构数组中存的是0, 1 该位为1,则表示是文件描述符的监视对象。
我们也可以猜想,这个结构是用位图来实现的。这也正是select的优点,我们后边详细说明。
我们来看看具体怎么使用fd_set;
下面提供了宏来处理这几种描述词组
fd 为select的句柄
fd_set *set 表示指向结构数组的指针
FD_CLR() : 将set清零,使集合中不含任何fd
FD_ISSET() : 调用select,后用它检测fd是否在集合,存在则返回真,否则返回假(0).
FD_SET (): 将fd加入到集合中
FD_ZERO() : 将fd从集合中清除
总结下select函数的调用过程
先调用宏 FD_ZERO 将指定的 fd_set 清零,然后调用宏 FD_SET 将需要测试的 fd 添加进 fd_set,接着调用函数 select 测试 fd_set 中的所有fd,最后用宏 FD_ISSET 检查某个 fd 在函数select调用后,相应位是否仍然为1。
select模型的优缺点
优点:
(1)select相比较与多进程多线程的服务器来说可以一次等待多个文件描述符;
(2)当用户数量比较多是,占用的资源比较恒定,性能比较好;
缺点:
(1)一次所监视的文件描述符的数量有限,默认是1024;
(2)由于参数为输入/输出型参数,每次等待都需要对于文件句柄集进行遍历重置,当文件描述符的数目增多时,遍历的开销变大,性能会下降;
(3)用户数量增多时,select会频繁触发从内核态到用户态,从用户态到内核态对fd的拷贝,会导致性能的下降;
代码模拟:
1 #include <stdio.h> 2 #include <sys/time.h> 3 #include <sys/types.h> 4 #include <sys/select.h> 5 #include <sys/socket.h> 6 #include <arpa/inet.h> 7 #include <netinet/in.h> 8 #include <unistd.h> 9 #include <stdlib.h> 10 11 int rfds[128]; 12 13 static void usage(const char* proc) 14 { 15 printf("Usage:%s[local_ip][local_port]\n", proc); 16 } 17 18 int startup(const char* ip, int port) 19 { 20 int sock = socket(AF_INET, SOCK_STREAM, 0); 21 if(sock < 0) 22 { 23 perror("socket"); 24 return 1; 25 } 26 struct sockaddr_in local_server; 27 local_server.sin_family = AF_INET; 28 local_server.sin_port = htons(port); 29 local_server.sin_addr.s_addr = inet_addr(ip); 30 31 int opt = 1; 32 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 33 34 if(bind(sock, (struct sockaddr*)&local_server, sizeof(local_server)) < 0) 35 { 36 perror("bind"); 37 return 2; 38 } 39 40 if(listen(sock, 5) < 0) 41 { 42 perror("listen"); 43 return 3; 44 } 45 46 return sock; 47 } 48 49 int main(int argc, char*argv[]) 50 { 51 if(argc != 3) 52 { 53 usage(argv[0]); 54 return 1; 55 } 56 57 int listen_sock = startup(argv[1], atoi(argv[2])); 58 59 int i = 0; 60 for(; i < 128; i++) 61 { 62 rfds[i] = -1; 63 } 64 65 fd_set rset; 66 int max = 0; 67 68 while(1) 69 { 70 struct timeval timeout = {0, 0}; 71 FD_ZERO(&rset); 72 73 rfds[0] = listen_sock; 74 max = listen_sock; 75 76 for(i = 0; i < 128; i++) 77 { 78 if(rfds[i] >= 0) 79 { 80 FD_SET(rfds[i], &rset); 81 if(max < rfds[i]) 82 { 83 max = rfds[i]; 84 } 85 } 86 } 87 88 switch(select(max + 1, &rset, NULL, NULL, NULL)) 89 { 90 case -1: 91 perror("select"); 92 break; 93 case 0: 94 printf("timeout...\n"); 97 { 98 int j = 0; 99 for(; j < 128; j++)100 {101 if(rfds[j] < 0)102 {103 continue;104 }105 if((j == 0) && FD_ISSET(rfds[j], &rset))106 {107 struct sockaddr_in client;108 socklen_t len = sizeof(client);109 int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);110 if(new_sock < 0)111 {112 perror("accept");113 }114 else115 {117 118 int k = 0;119 for(; k < 128; k++)120 {121 if(rfds[j] == -1)122 {123 rfds[k] = new_sock;124 break;125 }126 }127 if(k == 128)128 {129 close(new_sock);130 }131 }132 }133 else if(FD_ISSET(rfds[j], &rset))134 {135 char buf[1024];136 ssize_t s = read(rfds[j], buf, sizeof(buf)-1);137 if(s > 0)138 {139 buf[s] = 0;140 printf("client# %s\n", buf);141 }142 else if(s == 0)143 {144 printf("the client is quit!\n");145 close(rfds[j]);146 rfds[j] = -1;147 }148 else149 {150 perror("read");151 }152 }153 }154 }155 break;156 }157 }158 return 0;159 }
- 模拟实现select服务器
- Select实现并发服务器
- select服务器简单实现
- select实现并发服务器
- Select服务器代码实现
- select服务器的实现
- select服务器的实现
- web服务器模拟实现
- select 实现 tcp echo 服务器
- 服务器Select模型的实现
- select 实现的 socket服务器
- 【网络】Select服务器的实现
- Jquery实现div模拟Select控件
- jquery模拟实现仿select效果
- 用li模拟select实现下拉框
- 模拟select下拉实现多选
- 模拟select
- 模拟 SELECT
- Activiti相关信息
- 常用数据结构及算法
- C++继承与派生(公有派生和私有派生)的概念
- Spring boot JSP 打包执行问题
- monkey测试命令行
- 模拟实现select服务器
- English story 14
- zookeeper问题记录
- HDU 6078 2017 Multi-University Training Contest
- 如何使用socket实现unity和Winform之间通信
- UIImagePickerController使用
- 小白学习日记1:PL/SQL连接本地Oracle数据库Part1
- log4j管理日志
- 线段相交判断