在《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》中我们分析了朴素模型的一个缺陷——一次只能处理一个连接。本文介绍的Select模型则可以解决这个问题。(转载请指明出于breaksoftware的csdn博客)

和朴素模型一样,我们首先要创建一个监听socket,然后调用listen去监听服务器端口。不同的是,我们要对make_socket方法传递1,因为我们要创建一个异步的socket。

 listen_sock = make_socket(1);if (listen(listen_sock, SOMAXCONN) < 0) {perror("listen error");exit(EXIT_FAILURE);}

下一步我们需要清空Select模型使用的fd_set结构体对象,并把监听socket设置进去

 FD_ZERO(&active_fd_set);FD_SET(listen_sock, &active_fd_set);

active_fd_set用于记录活动的socket信息。之后我们使用到的read_fd_set则是其一个拷贝,因为我们只关心读行为

 fd_set active_fd_set, read_fd_set;

和朴素模型类似,我们也需要使用一个死循环让服务器不要停止

 /* Initialize the set of active sockets. */while (1) {timeout.tv_sec = 0;timeout.tv_usec = 500;/* Service all the sockets with input pending. */read_fd_set = active_fd_set;switch(select(FD_SETSIZE, &read_fd_set, NULL, NULL, &timeout)) {case -1 : {perror("select error\n");exit(EXIT_FAILURE);}break;case 0 : {//perror("select timeout\n");}break;default: {

select函数第一个参数我们传递了FD_SETSIZE,它在我的系统上是1024,它代表需要关注的socket的最大个数。第二参数是用于记录需要关注的发生读事件的fd_set对象。我们让select函数按异步方式执行,故最后一个参数设置为500微秒的超时时间。整个select函数意思是我们需要等待socket发生可读事件,如果等待时间超过超时设置,则函数返回0,如果出错则返回-1,如果等待到事件则返回其他值。

         default: {for (index = 0; index < FD_SETSIZE; ++index) {if (FD_ISSET(index, &read_fd_set)) {if (listen_sock == index) {/* Connection request on original socket. */int new_sock;new_sock = accept(listen_sock, NULL, NULL);if (new_sock < 0) {perror("accept error");exit(EXIT_FAILURE);}request_add(1);//set_block_filedes_timeout(new_sock);FD_SET(new_sock, &active_fd_set);} else {if (0 == server_read(index)) {server_write(index);}close(index);FD_CLR(index, &active_fd_set);}}}}}}return 0;
}

default中才是我们程序的重点。

我们使用一个for循环遍历每个socket。如果该socket通过FD_ISSET宏判断不处于我们关注的可读事件fd_set中,则忽略它。

如果处在可读fd_set中,则看看其是否是监听socket。
        如果是监听socket,则使用accpet方法获取接入的socket。并使用request_add让请求数量加一。还要使用FD_SET宏将该socket加入到活动状态的fd_set中。之后该活动状态的fd_set将被赋值给需要关注可读事件的fd_set中。

如果不是监听socket,则是接入的socket。于是我们调用《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中介绍的server_read和server_write方法读取内容并回包。最后我们还要关闭socket,并使用FD_CLR宏将该socket从活动状态的fd_set中去掉。之后的select函数将不会在关注该socket了。

整个过程非常简单。但是这其中却包含了很多值得思考的问题。

首先我抛出一个问题,我在default中使用了一个从0到FD_SETSIZE的遍历行为。并且将遍历的游标——index作为socket去操作——使用server_read和server_write去读取。于是问题就来了,使用make_socket创建的socket值和使用accept接收到的socket的值怎么和游标产生关联?代码中似乎没有任何让它们产生关联的逻辑,而且它们的关系是严格的“相等”的关系!那么只有一个假设,就是make_socket和accept返回的socket值在FD_SETSIZE和0之间。但是目前我没有找到文档对这个问题进行说明,而我也没深入研究这两个函数考证到其值就是在这个范围之内,那么为什么我还要这么去用呢?

我们先记下这个问题,深入到linux的源码中取解释这个使用的正确性。

我们先看下fd_set的定义

/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;/* Some versions of <linux/posix_types.h> define this macros.  */
#undef  __NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS   (8 * (int) sizeof (__fd_mask))
#define __FD_ELT(d) ((d) / __NFDBITS)
#define __FD_MASK(d)    ((__fd_mask) 1 << ((d) % __NFDBITS))/* fd_set for select and pselect.  */
typedef struct{/* XPG4.2 requires this member name.  Otherwise avoid the namefrom the global namespace.  */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;/* Maximum number of file descriptors in `fd_set'.  */
#define FD_SETSIZE      __FD_SETSIZE

以上我是在我ubuntu系统的/usr/include/x86_64-linux-gnu/sys/select.h文件中找到的定义。我们看到fd_set的主体就是一个long int型数组__fds_bits。该数组的个数是两个数的商。被除数__FD_SETSIZE就是我们程序中使用的FD_SETSIZE,也就是1024。除数__NFDBITS是64。于是fd_set中数组元素的个数是1024/64=16。注意一下这个值是16,而我们程序中关注的socket的最大个数是FD_SETSIZE——1024,这是为什么?其实这就是该结构设计的一个精妙之处。fd_set的__fds_bits是一个16个元素的long int型数组,其总长度就是16*64=1024位。于是可以使用每一位表示一个socket。

我们到/usr/include/x86_64-linux-gnu/bits/select.h 文件中看看linux是如何让socket和这个空间中每一位进行对应的。我们查看FD_SET宏

#define __FD_SET(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))

__FDS_BITS宏定义在fd_set定义中。它就是返回fd_set的__fds_bits数组首地址。数组的游标是通过__FD_ELT对socket进行处理的结果。__FD_ELT在上面我们已经见过,它是对socket值除以__NFBITS——64的值。通过游标我们取到数组元素值之后,我们再用其与__FD_MASK对socket进行操作的值进行或操作。__FD_MASK的定义也在上面给出,它是将socket的值与__NFBITS——64相除,取得余数,然后让1左移该余数次。这样我们就将该socket映射到fd_set内存的一位中。我们知道,只要在知道除数、商和余数的情况下,可以很方便的推算出被除数是多少。可以说linux内核对这块的设计真是做到了极致,不浪费一点点空间。

有了上面的认识,我们就知道select模型最大只能支持FD_SETSIZE个数的socket,而且socket的值也只能在FD_SETSIZE之内。如果socket()或accept()函数返回的socket值大于FD_SETSIZE,则select模型将出现错误——上面的计算将溢出。基于这种反向推理,我们可以放心大胆的使用0到FD_SETSIZE的值去当socket的值去计算。我看网上有很多select例子需要使用一个数组去维护接入的socket,如果在不考虑效率的前提下是不必要的。但是如果你追求极致的Select模型性能,还是建议使用一个数组去维护Socket,这样不至于出现大量的浪费操作。这块分析我们将在后文中给出。

这儿再多言一句,正是因为这种位操作,我们才需要在使用fd_set之前调用FD_ZERO去清空所有空间

# if __WORDSIZE == 64
#  define __FD_ZERO_STOS "stosq"
# else
#  define __FD_ZERO_STOS "stosl"
# endif# define __FD_ZERO(fdsp) \do {                                         \int __d0, __d1;                                \__asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS                \: "=c" (__d0), "=D" (__d1)               \: "a" (0), "0" (sizeof (fd_set)            \/ sizeof (__fd_mask)),         \"1" (&__FDS_BITS (fdsp)[0])                  \: "memory");                         \} while (0)

如果socket不再需要监测,则我们使用__FD_CLR在fd_set中去除其对应的位

#define __FD_CLR(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))

我们再来看下Select模型的处理能力。我们采用和《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中相同的环境和压力,看下服务器的数据输出

再看下客户端的输出


        可见当前环境下,select模型的处理能力大概是每秒7000多连接。(和下一章介绍的Poll模型差距不大,而且如果使用数组维护Socket还可以提高性能)

朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型相关推荐

  1. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--模型比较>一文中,我们分析了各种模型在处理短连接时的能力.本文我们将讨论处理长连接时各个模型的性能.(转载请指明出于b ...

  2. 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

    经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤.现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客) 模型编程步骤对比 <朴素.Select.Poll和Ep ...

  3. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型

    在阅读完<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>和<朴素.Select.Poll和Epoll网络编程模型实现和分析--Poll模型> ...

  4. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>中,我们分析了它只能支持1024个连接同时处理的原因.但是在有些需要同时处理更多连接的情况下,102 ...

  5. 朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型

    做Linux网络开发,一般绕不开标题中几种网络编程模型.网上已有很多写的不错的分析文章,它们的基本论点是差不多的.但是我觉得他们讲的还不够详细,在一些关键论点上缺乏数据支持.所以我决定好好研究这几个模 ...

  6. Linux下select, poll和epoll IO模型的详解

    http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...

  7. Linux中select poll和epoll的区别

    首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的.教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈-我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转 ...

  8. 基于linux epoll网络编程细节处理丨epoll原理剖析

    epoll原理剖析以及三握四挥的处理 1. epoll原理详解 2. 连接的创建与断开 3. epoll如何连接细节问题 视频讲解如下,点击观看: 基于linux epoll网络编程细节处理丨epol ...

  9. 【Linux网络编程】并发服务器之select模型

    00. 目录 文章目录 00. 目录 01. 概述 02. I/O复用技术概述 03. select模型服务器实现思路 04. select模型服务器实现 05. 附录 01. 概述 服务器设计技术有 ...

最新文章

  1. Ubuntu下使用Anaconda安装opencv 解决无法读取视频
  2. 手动配置 ESXi 主机挂载 NFS 的最大值
  3. 深入解析阿里 PouchContainer 如何实现容器原地升级
  4. 华夏基金专访神策数据创始人兼 CEO 桑文锋,金融科技数字化趋势认知传递
  5. 路径总和 III—leetcode437
  6. vlc 在ubuntu 14下的linux版本编译
  7. 将一个输入流(InputStream)写入到一个文件中
  8. 瑞幸咖啡 CEO 和 COO 被暂停职务;快手起诉抖音索赔 500 万元;Wine 5.8 发布 | 极客头条...
  9. 图片上传,CheckBox等用户控件的应用代码
  10. oracle建表 和 设置主键自增
  11. XAMPP 使用教程
  12. sublime text 显示 typescript高亮
  13. 豆瓣关于计算机视觉的书评及介绍
  14. 超级APP势不可挡--20190706
  15. python stm32f401_STM32学习之GPIO配置 (STM32F401ZGT6)
  16. web border属性
  17. UA MATH524 复变函数4 复级数与复变函数的积分基础
  18. 装机必备的浏览器推荐,干净好用,选这4款不会出错
  19. netcore 集成 CAP 使用 rabbitMQ集群
  20. python flask上传Excel并把Excel的数据导入数据库

热门文章

  1. 使用Python和OpenCV构建图像金字塔
  2. 概率论—随机变量的数字特征、大数定律及中心极限定理
  3. 1行Python代码制作动态二维码
  4. linux怎么开启samba服务,LINUX开启SAMBA服务
  5. 威纶通触摸屏与单片机MODBUS_威纶通案例集锦51-55
  6. php zblog 侧边栏样式_zblogphp版如何实现导航栏下拉框
  7. SketchUp Pro 2021基础入门学习视频教程
  8. grep 在HP-UX下的递归查找
  9. C++的 STL堆 实现获取中位数
  10. NAT技术和代理服务器