TCP/IP网络编程 第十二章:I/O复用

news2025/2/2 23:57:57

基于I/O复用的服务器端

多进程服务器端的缺点和解决方法

为了构建并发服务器,只要有客户端连接请求就会创建新进程。这的确是实际操作中采用的种方案,但并非十全十美,因为创建进程时需要付出极大代价。这需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。各位应该也感到需要IPC时会提高编程难度。“那有何解决方案?能否在不创建进程的同时向多个客户端提供服务?”


当然能!本节讲解的I/O复用就是这种技术。大家听到有这种方法是否感到一阵兴奋?但请不要过于依赖该模型!该方案并不适用于所有情况,应当根据目标服务器端的特点采用不同实现方法。下面先理解“复用”(Multiplexing)的意义。

理解复用

在网络编程中,复用(Multiplexing)是指在一个物理通信链接(如网络传输介质)上同时传输多个独立的数据流。它通过将多个数据流合并成一个流并在接收端将其分解,从而提高网络资源的利用效率。

复用技术可以通过以下几种方式实现:

  1. 时间复用(Time Division Multiplexing,TDM):将时间划分为若干个间隔,每个间隔分配给不同的数据流进行传输。发送端按照一定的规则在每个时间间隔内发送数据,接收端则根据间隔进行数据的提取和恢复。

  2. 频分复用(Frequency Division Multiplexing,FDM):将频率范围划分为多个窄带信道,每个信道专门用于传输一个数据流。数据流经过调制后,在不同的频率上进行传输,接收端则对信号进行解调得到原始数据。

  3. 码分复用(Code Division Multiplexing,CDM):利用不同的码序列来区分各个数据流。发送端使用特定的码序列对数据进行扩展,接收端则使用相同的码序列进行解扩,从而将数据流分离。

以上这些复用技术都旨在实现多个数据流在同一物理通信链接上的传输,以提高网络的带宽利用率和传输效率。在网络编程中,我们可以使用不同的复用技术来实现同时处理多个客户端请求或在单个连接上传输多个数据流。

复用技术在服务器端的应用

对于网络编程中的IO复用(IO multiplexing),它是一种高效处理多个IO事件的机制。传统的IO模型中,每个IO操作都会阻塞线程,导致程序在处理一个IO时无法同时处理其他IO事件,造成资源浪费。

而IO复用通过利用特定的系统调用函数(如select、poll、epoll等)来监视多个IO事件,将多个IO操作集中在一个线程中进行管理和处理,从而实现同时处理多个IO事件的能力。它的基本原理是将需要监听的IO事件加入到一个事件集合中,然后通过系统调用阻塞等待其中任何一个事件就绪,一旦有就绪事件,程序就可以执行相应的操作。

IO复用的主要好处包括:

  1. 资源利用率高:使用IO复用可以避免每个IO操作都阻塞线程,从而减少线程数量,提高了资源利用效率。

  2. 响应速度快:IO复用可以同时监听多个IO事件,一旦有事件就绪,立即进行处理,大大减小了事件响应的延迟。

  3. 编程简洁:相比于多线程或多进程模型,使用IO复用可以简化代码,降低开发和维护的难度。

总而言之,IO复用是一种高效处理多个IO事件的机制,它可以减少线程数量、提高资源利用率和响应速度,是网络编程中常用的技术之一。

我再举个例子来理解一下IO复用服务器端,某教室中有10名学生和1位教师,这些孩子并非等闲之辈,上课时不停地提问。学校没办法,只能给每个学生都配1位教师,也就是说教室中现有10位教师。此后,只要有新的转校生,就会增加1位教师,因为转校生也喜欢提问。这个故事中,如果把学生当作客户端,把教师当作与客户端进行数据交换的服务器端进程,则该教室的运营方式为多进程服务器端方式。


有一天,该校来了位具有超能力的教师。这位教师可以应对所有学生的提问,而且回答速度很快,不会让学生等待。因此,学校为了提高教师效率,将其他老师转移到了别的班。现在,学生提问前必须举手,教师确认举手学生的提问后再回答问题。也就是说,现在的教室以IO复用方式运行。
虽然例子有些奇怪,但可以通过它理解IO复用方法:教师必须确认有无举手学生,同样,IO复用服务器端的进程需要确认举手(收到数据)的套接字,并通过举手的套接字接收数据。

理解select函数并实现服务器端

运用select函数是最具代表性的实现复用服务器端方法。Windows平台下也有同名函数提供相
同功能,因此具有良好的移植性。

select函数的功能和调用顺序

使用sclect函数时可以将多个文件描述符集中到一起统一监视,项目如下。
□是否存在套接字接收数据?
□无需阻塞传输数据的套接字有哪些?
□哪些套接字发生了异常?

select函数的使用方法与一般函数区别较大,更准确地说,它很难使用。但为了实现IO复用服务器端,我们应掌握select函数,并运用到套接字编程中。认为“select函数是IO复用的全部内容”也并不为过。接下来介绍select函数的调用方法和顺序。

步骤一:
设置文件描述符
指定监视范围
设置超时
步骤二:
调用select函数
步骤三:
查看调用结果

可以看到,调用select函数前需要一些准备工作,调用后还需查看结果。接下来按照上述顺序逐一讲解。

设置文件描述符

利用select函数可以同时监视多个文件描述符。当然,监视文件描述符可以视为监视套接字.此时首先需要将要监视的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常进行区分,即按照上述3种监视项分成3类。


使用fd_set数组变量执行此项操作,如图所示。该数组是存有0和1的位数组。

 图中最左端的位表示文件描述符0(所在位置)。如果该位设置为1,则表示该文件描述
符是监视对象。那么图中哪些文件描述符是监视对象呢?很明显,是文件描述符1和3。
“是否应当通过文件描述符的数字直接将值注册到fd_set变量?”
当然不是!针对fd_ set变量的操作是以位为单位进行的,这也意味着直接操作该变量会比较
繁琐。难道要求各位自己完成吗?实际上,在fd_set变量中注册或更改值的操作都由下列宏完成

□FD_ZERO(fd_set *fdset):将fd_set变量的所有位初始化为0。
□ FD_SET(int fd, fd_set *fdset):在参数fdset指向的变量中注册文件描述符的信息。

□ FD_CLR(int fd, fd_set *fdset):从参数fdset指向的变量中清除文件描述符的信息。

□ FD_ISSET(int fd,fd_set *fdset):若参数fdset指向的变量中包含文件描述符的信息,则返回“真”。
上述函数中,FD_ISSET用于验证select函数的调用结果。

设置检查(监视)范围及超时

先简单的介绍一下select函数。

#include<sys/select.h>
#include<sys/time.h>

int select(int maxfd,fd_set* readset,fd_set* writeset,fd_set* exceptset,const struct timeval *timeout);//成功时返回大于0的值,失败时返回-1
      maxfd     //监视对象文件描述符数量。
      readset   //将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。 
      writeset  //将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。  
      exceptset //将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值。
      timeout   //调用select函数后,为防止陷入无限阻塞的状态,传递超时(time-out)信息。
      返回值    //发生错误时返回-1,超时返回时返回0。因发生关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数

如上所述,selcct函数用来验证3种监视项的变化情况。根据监视项声明3个fd_set型变量,分别向其注册文件描述符信息,并把变量的地址值传递到上述函数的第二到第四个参数。但在此之前(调用select函数前)需要决定下面2件事。
“文件描述符的监视(检查)范围是?”
“如何设定select函数的超时时间?”
第一,文件描述符的监视范围与select函数的第一个参数有关。实际上,select函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在fd_set变量中的文件描述符数。但每次新建文件描述符时,其值都会增1,故只需将最大的文件描述符值加1再传递到select函数即可。加1是因为文件描述符的值从0开始。
第二,select函数的超时时间与select函数的最后一个参数有关,其中timeval结构体定义如下。

struct timeval{
     long tv_sec;   //seconds
     long tv_usec;  //microseconds
}

本来select函数只有在监视的文件描述符发生变化时才返回。如果未发生变化,就会进入阻塞状态。指定超时时间就是为了防止这种情况的发生。通过声明上述结构体变量,将秒数填入tv_sec成员,将微秒数填入tv_usec成员,然后将结构体的地址值传递到select函数的最后一个参数。此时,即使文件描述符中未发生变化,只要过了指定时间,也可以从函数中返回。不过这种情况下,select函数返回0。因此,可以通过返回值了解返回原因。如果不想设置超时,则传递NULL参数。

调用select函数后查看结果

函数调用后查看结果也同样重要。我们已经讨论过select函数的返回值,如果返回了大于0的整数,说明相应数量的文件描述符发生变化。那么这个变化是如何变化的呢?select函数调用完成过后,向其传递的fd_set变量中将发生变化。原来为1的所有位均变为0,但发生变化的文件描述符对应位除外。因此,可以认为值仍为1的位置上的文件描述符发生了变化。

select函数调用示例

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/select.h>
#define BUF_SIZE 30

int main(int argc,char *argv){
    fd_set reads,temps;
    int result,str_len;
    char buf[BUF_SIZE];
    struct timeval timeout;

    FD_ZERO(&reads);
    FD_SET(0,&reads);

    while(1){
       temps=reads;
       timeout.tv_sec=5;
       timeout.tv_usec=0;
       result=select(1,&temp,0,0,&timeout);
       if(result==-1){
            puts("select() error!");
            break;
       }
       else if(result==0){
            puts("Time-out!");
       }
       else{
            if(FD_ISSET(0,&temps)){
               str_len=read(0,buf,BUF_SIZE);
               buf[str_len]=0;
               printf("message from console: %s",buf);
            }
       }
    }
return 0;
}

上述内容有两个点要注意:

1.由于select函数调用后会修改监视fd_set中的内容,所以我们需要保存一份副本,上述代码保存在temps中。

2.由于select函数调用时,timeout中的时间最后会被替换为超时前剩余时间,所以这个timeout初始量也需要每次初始化。

实现IO复用服务器端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<sys/select.h>

#define BUF_SIZE 100
void error_handling(char *buf);

int main(int argc,char *argv[]){
    int serv_sock,clnt_sock;
    struct sockaddr_in serv_addr,clnt_addr;
    struct timeval timeout;
    fd_set reads,cpy_reads;

    socklen_t addr_sz;
    int fd_max,str_len,fd_num,i;
    char buf[BUF_SIZE];
    if(argc!=2){
         printf("Usage: %s <port>\n",argv[0]);
         exit(1);
    }

    serv_sock=socket(PF_INET,SOCK_STREAM,0);
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi([argv[1]]));

    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
         error_handling("bind() error");
    if(listen(serv_sock,5)==-1)
         error_handling("listen() error");

    FD_ZERO(&reads);
    FD_SET(serv_sock,&reads);
    fd_max=serv_sock;

    while(1){
         cpy_reads=reads;
         timeout.tv_sec=5;
         timeout.tv_usec=5000;

         if((fd_num=select(fd_max+1,&cpy_reads,0,0,&timeout))==-1)
              break;
         if(fd_num==0)
              continue;

         for(i=0;i<fd_max+1;++i){
              if(FD_ISSET(i,&cpy_reads)){     
                     if(i==serv_sock){     //连接请求到来
                         addr_sz=sizeof(clnt_addr);
                         clnt_sock=accept(serv_sock,(structsockaddr*)&clnt_addr,addr_sz);
                         FD_SET(clnt_sock,&reads);
                         if(fd_max<clnt_sock)fd=clnt_sock;
                         printf("connected client: %d \n",clnt_sock);
                     }
                     else{
                         str_len=rad(i,buf,BUF_SIZE);
                         if(str_len==0){
                               FD_CLR(i,&reads);
                               close(i);
                               printf("closed client: %d \n",i);
                         }
                         else{
                             write(i,buf,str_len);//回声
                         }
                     }
              }
         }
    }
    close(serv_sock);
    return 0;
}

void error_handling(char *buf){
    fputs(buf,stderr);
    fputc('\n',stderr);
    exit(1);
}

基于Windows的实现

在Windows平台调用select函数

Windows同样提供select函数,而且所有参数与Linux的select函数完全相同。只不过Window平台select函数的第一个参数是为了保持与(包括Linux的)UNIX系列操作系统的兼容性而添加的,并没有特殊意义。

#include <winsock2.h>
int select(int nfds, fd_set *treadfds, fd_set *writefds, fd_set *excepfds, const struct
timeval * timeout);//成功时返回0,失败时返回-1。

返回值、参数的顺序及含义与之前的Linux中的select函数全相同,故省略。下面给出timeval
结构体定义。

typedef struct timeval{
      long tv_sec;
      long tv_usec;
} TIMEVAL;

可以看到,基本结构与之前Linux中的定义相同,但Windows中使用的是typedef声明。接下来观察fd_set结构体。Windows中实现时需要注意的地方就在于此。可以看到,Windows的fd_set并非像Linux中那样采用了位数组。

typedef struct fd_set{
    u_int fd_count;
    SOCKET fd_array[FD_SETSIZE];
} fd_set;

Windows的fd_set由成员fd_count和fd_array构成,fd_count用于接字句柄数,fd_array用于保存套接字句柄,只要略加思考就能理解这样声明的原因。Linux的文件描述符从0开始递增,因此可以找出当前文件描述符数量和最后生成的文件描述符之间的关系。但Windows的套接字句柄并非从0开始,而且句柄的整数值之间并无规律可循,因此需要直接保存句柄的数组和记录句柄数的变量。幸好处理fd_set结构体的FDXXX型的4个宏的名称、功能及使用方法与Linux完全相同(故省略),这也许是微软为了保证兼容性所做的考量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/762345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue列表排序

开始前先回顾一下sort排序用法&#xff1a; 定义一串数组arr&#xff0c;使用sort排序&#xff0c;会收到前后两个数据项设置两个参数a&#xff0c;b。 注意&#xff1a;a-b 是升序 b-a 是降序 a-b升序&#xff1a; <script>let arr [12,11,2,5,76,33]arr.sort((a,b…

家居商城小程序:打造舒适家居生活的优选平台

随着人们对家居生活品质的追求&#xff0c;家居商城小程序成为提供便捷购物和个性化服务的不可或缺的工具。通过家居商城小程序&#xff0c;用户可以浏览并购买各类家居商品&#xff0c;如家具、装饰品、家纺等。同时&#xff0c;家居商城小程序能提供热销商品推荐、客户评价和…

浅析高校用电问题及智慧电力监管平台的构建 安科瑞 许敏

摘 要&#xff1a;介绍了当前高校用电存在的问题&#xff0c;进行了原因分析&#xff0c;由此提出建立高校用电智慧监管平台。对高校用电智慧监管平台的构架进行设计&#xff0c;运用物联网技术&#xff0c;实现各回路实时自主控制&#xff0c;并细化管理权限&#xff0c;实现…

Swift 周报 第三十三期

文章目录 前言新闻和社区App 内购买项目和订阅即将实行价格与税率调整为家庭提供安全的 App 体验 提案正在审查的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组自主整理周报的第二十四期&#xff0c;每个模块已初步成型。各位读者如果有好的提议&#xff…

公网访问的Linux CentOS本地Web站点搭建指南

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 前言 在web项目中,部署的web站点需要被外部访问,则…

常规函数和箭头函数之间的主要区别

常规函数和箭头函数之间的主要区别 在 JavaScript 中&#xff0c;函数是设计用于执行特定任务的代码块。函数允许使用函数将大型程序分解为多个更小、更易于管理的组件。因此&#xff0c;我们就不再需要重复编写相同的代码。 JavaScript中有两种类型的函数 常规函数箭头函数…

jmeter如何进行web脚本录制

目录 录制web脚本 &#xff08;1&#xff09;jmeter中设置HTTP代理 &#xff08;2&#xff09;浏览器中设置代理 &#xff08;3&#xff09;页面操作 &#xff08;4&#xff09;查看录制的web脚本 &#xff08;5&#xff09;脚本内容过滤 &#xff08;6&#xff09;脚本优化…

练习 数列前n项和(递归函数)

C++自学精简教程 目录(必读) 数列的前n项和 S = 1 + 2 + 3 + ...... + n 之前我们用for循环求解数列前n项和,本文用递归函数求解。 代码如下 #include <iostream> using namespace std;int f(int a) {if (a == 1){return 1;}else{return a + f(a - 1);} }int main(…

APP外包开发中的H5框架

在开发APP的技术中&#xff0c;除了原生开发外也可以使用H5框架来开发。原生开发的特点是质量高&#xff0c;用户体验更好&#xff0c;但成本高&#xff0c;适用于对质量要求高的APP项目。H5框架的特点是通用性较强&#xff0c;对开发人员的要求相对较低&#xff0c;成本也低&a…

从小众到大热:海外网红营销的成功之道

在当今数字时代&#xff0c;社交媒体已经成为人们获取信息、沟通交流的主要渠道之一。而在这个社交媒体的浪潮中&#xff0c;海外网红营销逐渐从小众走向大热。它以其独特的营销模式和广泛的受众群体&#xff0c;成为许多品牌和企业的首选营销方式。本文Nox聚星将详细介绍海外网…

高等学校节能监控平台的具体应用 安科瑞 许敏

摘要&#xff1a;高校节能监控平台&#xff0c;主要是通过物联网技术实现对水、电、气等高耗能设备进行计量和控制&#xff0c;为高校能耗的分析&#xff0c;能源流向&#xff0c;节能目标提供有力的数据支撑。高效节能监控平台主要包括能耗监测系统、照明节能控制系统、空调节…

Gtest在ARM平台上的离线搭建(让Gtest编译安装成功之后的可执行文件.so变成ARM下的—ARM aarch64)(实用篇)

编译时自动调用Cunit或者Gtest的静态或者动态库文件说明拷贝Gtest安装包到新目录下根目录下创建build目录并且进行编译检查生成的库文件是否属于ARM架构下的将库文件拷贝到统一的ARM包下面编译时自动调用Cunit或者Gtest的静态或者动态库文件说明 这里之前在usr/local/lib下面安…

Springboot工程常见错误

1. mybatis的mapper.xml出现tag name expected错误 https://blog.csdn.net/watson2017/article/details/128902300 <符号在xml配置SQL 语句中是不能直接识别出来的&#xff0c;也就是说&#xff0c;我们在使用到 > 、< 等符号的时候&#xff0c;需要将其进行转义&…

LaTex 的基本使用方法

TeXstudio 设置新建和编辑文档编译和预览拼写检查宏和脚本 LaTex 分模块详解 LaTex 文件头 documentclass article&#xff1a;用于写短篇文章、报告report&#xff1a;用于写长篇报告、学位论文、技术报告等book&#xff1a;用于编写书籍&#xff0c;具有章节、子章节和节的…

leetcode 77. 组合

2023.7.17 今天正式开始回溯系列&#xff0c;这是一道经典回溯题。 先上一个经典回溯模板&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点…

飞行动力学 - 第10节-空间机动性、稳定性与操纵性概述 之 基础点摘要

飞行动力学 - 第10节-空间机动性、稳定性与操纵性概述 之 基础点摘要 1. 协调盘旋性能计算流程2. 一般盘旋2.1 动力学方程2.2 角点速度2.3 典型战斗机盘旋曲线 3. 空间机动能力4. 飞行动力学&#xff1a;飞行性能稳定性与操纵性5. 稳定性定义6. 飞行品质6.1 品质等级6.2 品质评…

大数据测试之数据仓测试怎么做(上)

前面的文章我们为大家介绍了大数据测试平台和大数据系统的测试方法&#xff0c;接下来我们重点来讲一下数据仓库测试&#xff0c;首先看一下它的定义。 数据仓库(Data Warehouse)&#xff1a;一个面向主题的&#xff08;Subject Oriented&#xff09;、集成的 &#xff08;In…

给学弟妹们的 10 个秋招建议!

大家好&#xff0c;我是鱼皮。最近很多大公司的提前批陆陆续续开启了&#xff0c;说明秋招已经拉开了序幕&#xff0c;大家要准备起来了。 所以我也赶紧写了一篇文章&#xff0c;结合自己曾经大厂求职的经验&#xff0c;并且从招聘方的角度&#xff0c;给学弟妹们一些秋招找工…

机器人架构设计和中间件

一&#xff0e;引言 在无人驾驶与机器人领域&#xff0c;算法一直都是研究的核心。无论是导航技术、控制技术&#xff0c;还是识别技术都是构成其技术栈的重要组成部分。但是&#xff0c;随着技术的发展&#xff0c;开发者们逐渐认识到一个问题&#xff0c;即程序本身的组织架构…

善用AI; AI生成美女图片中隐藏汉字,光影艺术引爆网络讨论

&#x1f989; AI新闻 &#x1f680; AI生成美女图片中隐藏汉字&#xff0c;光影艺术引爆网络讨论 摘要&#xff1a;近期&#xff0c;一组AI生成的美女图片在网络上疯传&#xff0c;用户发现这些图片中隐藏了汉字。这种光影艺术效果引起了广泛关注和讨论。AI绘画工具ControlN…