模拟实现select服务器

什么是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                             else
115                             {
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                             else
149                             {
150                                 perror("read");
151                             }
152                         }
153                     }
154                 }
155                 break;
156         }
157     }
158     return 0;
159 }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值