朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
在《朴素、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模型相关推荐
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较
在<朴素.Select.Poll和Epoll网络编程模型实现和分析--模型比较>一文中,我们分析了各种模型在处理短连接时的能力.本文我们将讨论处理长连接时各个模型的性能.(转载请指明出于b ...
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较
经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤.现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客) 模型编程步骤对比 <朴素.Select.Poll和Ep ...
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
在阅读完<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>和<朴素.Select.Poll和Epoll网络编程模型实现和分析--Poll模型> ...
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
在<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>中,我们分析了它只能支持1024个连接同时处理的原因.但是在有些需要同时处理更多连接的情况下,102 ...
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型
做Linux网络开发,一般绕不开标题中几种网络编程模型.网上已有很多写的不错的分析文章,它们的基本论点是差不多的.但是我觉得他们讲的还不够详细,在一些关键论点上缺乏数据支持.所以我决定好好研究这几个模 ...
- Linux下select, poll和epoll IO模型的详解
http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...
- Linux中select poll和epoll的区别
首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的.教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈-我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转 ...
- 基于linux epoll网络编程细节处理丨epoll原理剖析
epoll原理剖析以及三握四挥的处理 1. epoll原理详解 2. 连接的创建与断开 3. epoll如何连接细节问题 视频讲解如下,点击观看: 基于linux epoll网络编程细节处理丨epol ...
- 【Linux网络编程】并发服务器之select模型
00. 目录 文章目录 00. 目录 01. 概述 02. I/O复用技术概述 03. select模型服务器实现思路 04. select模型服务器实现 05. 附录 01. 概述 服务器设计技术有 ...
最新文章
- Ubuntu下使用Anaconda安装opencv 解决无法读取视频
- 手动配置 ESXi 主机挂载 NFS 的最大值
- 深入解析阿里 PouchContainer 如何实现容器原地升级
- 华夏基金专访神策数据创始人兼 CEO 桑文锋,金融科技数字化趋势认知传递
- 路径总和 III—leetcode437
- vlc 在ubuntu 14下的linux版本编译
- 将一个输入流(InputStream)写入到一个文件中
- 瑞幸咖啡 CEO 和 COO 被暂停职务;快手起诉抖音索赔 500 万元;Wine 5.8 发布 | 极客头条...
- 图片上传,CheckBox等用户控件的应用代码
- oracle建表 和 设置主键自增
- XAMPP 使用教程
- sublime text 显示 typescript高亮
- 豆瓣关于计算机视觉的书评及介绍
- 超级APP势不可挡--20190706
- python stm32f401_STM32学习之GPIO配置 (STM32F401ZGT6)
- web border属性
- UA MATH524 复变函数4 复级数与复变函数的积分基础
- 装机必备的浏览器推荐,干净好用,选这4款不会出错
- netcore 集成 CAP 使用 rabbitMQ集群
- python flask上传Excel并把Excel的数据导入数据库