模拟实现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 }
原创粉丝点击