TCP/IP网络编程——I/O 复用

news2024/11/24 4:34:06

完整版文章请参考:
TCP/IP网络编程完整版文章

文章目录

    • 第 12 章 I/O 复用
      • 12.1 基于 I/O 复用的服务器端
        • 12.1.1 多进程服务端的缺点和解决方法
        • 12.1.2 理解复用
        • 12.1.3 复用技术在服务器端的应用
      • 12.2 理解 select 函数并实现服务端
        • 12.2.1 select 函数的功能和调用顺序
        • 12.2.2 设置文件描述符
        • 12.2.3 设置检查(监视)范围及超时
        • 12.2.4 调用 select 函数查看结果
        • 12.2.5 select 函数调用示例
        • 12.2.6 实现 I/O 复用服务器端

第 12 章 I/O 复用

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

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

为了构建并发服务器,只要有客户端连接请求就会创建新进程。但是创建进程需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要采用相对复杂的方法,I/O 复用技术可以解决这个问题。

12.1.2 理解复用

在电子及通信工程领域,复用可以表示为:

在 1 个通信频道中传递多个数据(信号)的技术

复用的含义:

为了提高物理设备的效率,只用最少的物理要素传递最多数据时使用的技术

上述两种方法的内容完全一致。可以用纸电话模型做一个类比:

在这里插入图片描述

同时也可以简化为如下图的改进:

在这里插入图片描述

如图做出改进,就是引入了复用技术。

复用技术的优点:

  • 减少连线长度
  • 减少纸杯个数

即使减少了连线和纸杯的量仍然可以进行三人同时说话,但是如果碰到以下情况:

「好像不能同时说话?」

实际上,因为是在进行对话,所以很少发生同时说话的情况。也就是说,上述系统采用的是**「时分复用」**技术。因为说话人声频率不同,即使在同时说话也能进行一定程度上的区分(杂音也随之增多)。因此,也可以说是「频分复用技术」。

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

服务器端引入复用技术可以减少所需进程数。下图是多进程服务端的模型:

在这里插入图片描述

下图是引入复用技术之后的模型:

在这里插入图片描述

从图上可以看出,引入复用技术之后,可以减少进程数。重要的是,无论连接多少客户端,提供服务的进程只有一个

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

12.2.1 select 函数的功能和调用顺序

使用 select 函数时可以将多个文件描述符集中到一起统一监视,项目如下:

  • 是否存在套接字接收数据?
  • 无需阻塞传输数据的套接字有哪些?
  • 哪些套接字发生了异常?

术语:「事件」。当发生监视项对应情况时,称「发生了事件」。

select 函数的使用方法与一般函数的区别并不大,更准确的说,他很难使用。但是为了实现 I/O 复用服务器端,我们应该掌握 select 函数,并运用于套接字编程当中。认为「select 函数是 I/O 复用的全部内容」也并不为过。select 函数的调用过程如下图所示:

在这里插入图片描述

12.2.2 设置文件描述符

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

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

在这里插入图片描述

图中最左端的位表示文件描述符 0(所在位置)。如果该位设置为 1,则表示该文件描述符是监视对象。监视对象呢是描述符 1 和 3。在 fd_set 变量中注册或更改值的操作都由下列宏完成。

  • FD_ZERO(fd_set *fdset):将 fd_set 变量所指的位全部初始化成0
  • FD_SET(int fd,fd_set *fdset):在参数 fdset 指向的变量中注册文件描述符 fd 的信息
  • FD_CLR(int fd,fd_set *fdset):从参数 fdset 指向的变量中清除文件描述符 fd 的信息
  • FD_ISSET(int fd,fd_set *fdset):若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回「真」

上述函数中,FD_ISSET 用于验证 select 函数的调用结果,通过下图解释这些函数的功能:

在这里插入图片描述

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

下面是 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的值,该值是发生事件的文件描述符数。
*/

如上所述,select 函数用来验证 3 种监视的变化情况,根据监视项声明 3 个 fd_set 型变量,分别向其注册文件描述符信息,并把变量的地址值传递到上述函数的第二到第四个参数。但在此之前(调用 select 函数之前)需要决定下面两件事:

  1. 文件描述符的监视(检查)范围是?
  2. 如何设定 select 函数的超时时间?

第一,文件描述符的监视范围和 select 的第一个参数有关。实际上,select 函数要求通过第一个参数传递监视对象文件描述符的数量。因此,需要得到注册在 fd_set 变量中的文件描述符数。但每次新建文件描述符时,其值就会增加 1 ,故只需将最大的文件描述符值加 1 再传递给 select 函数即可。加 1 是因为文件描述符的值是从 0 开始的。

第二,select 函数的超时时间与 select 函数的最后一个参数有关,其中 timeval 结构体定义如下:

struct timeval
{
    long tv_sec;
    long tv_usec;
};

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

12.2.4 调用 select 函数查看结果

select 返回正整数时,怎样获知哪些文件描述符发生了变化?向 select 函数的第二到第四个参数传递的 fd_set 变量中将产生如图所示的变化:

由图可知,select 函数调用完成后,向其传递的 fd_set 变量将发生变化。原来为 1 的所有位将变成 0,但是发生了变化的文件描述符除外。因此,可以认为值仍为 1 的位置上的文件描述符发生了变化。

12.2.5 select 函数调用示例

下面是一个 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); //将文件描述符0对应的位设置为1

    /*
    timeout.tv_sec=5;
    timeout.tv_usec=5000;
    */

    while (1)
    {
        temps = reads; //为了防止调用了select 函数后,位的内容改变,先提前存一下
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        result = select(1, &temps, 0, 0, &timeout); //如果控制台输入数据,则返回大于0的数,没有就会超时
        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;
}

编译运行:

在这里插入图片描述

可以看出,如果运行后在标准输入流输入数据,就会在标准输出流输出数据,但是如果 5 秒没有输入数据,就提示超时。

12.2.6 实现 I/O 复用服务器端

下面通过 select 函数实现 I/O 复用服务器端。下面是基于 I/O 复用的回声服务器端。

#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 *message);

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

    socklen_t adr_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_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -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) //如果是服务端套接字时,受理连接请求
                {
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);

                    FD_SET(clnt_sock, &reads); //注册一个clnt_sock
                    if (fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("Connected client: %d \n", clnt_sock);
                }
                else //不是服务端套接字时
                {
                    str_len = read(i, buf, BUF_SIZE); //i指的是当前发起请求的客户端
                    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 *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

从图上可以看出,虽然只用了一个进程,但是却实现了可以和多个客户端进行通信,这都是利用了 select 的特点。

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

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

相关文章

anaconda下pytorchCPU GUP安装及问题记录

1 pytorch安装&#xff08;CPU版本&#xff09; pip3 install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple2 torchvision、torchaudio、torchtext安装&#xff1a;解决ModuleNotFoundError: No module named ‘torchvision‘问题 &#xff08…

用“AI“挑选一件智慧礼物

在久违的烟火气回归之际&#xff0c;充满希望的生活可能就从精心挑选一件新年礼物开始。在罗列礼品清单时&#xff0c;你会想到 “数据”也是其中之一吗&#xff1f;事实上&#xff0c;几乎所有时下最受欢迎的带有“智能”一词的设备&#xff0c;都是由大量高质量的数据创建。我…

面试必问的CAS,你懂多少?

目录一.什么是CAS&#xff1f;二.CAS实现过程三.CAS的缺点1.循环时间长2.只能保证一个共享变量是原子操作3.ABA问题和解决方法四.拓展题1.i和i是原子性操作吗&#xff1f;2. i 不加lock和synchronized怎么保证原子性&#xff1f;一.什么是CAS&#xff1f; CAS(Compare And Swa…

uboot源码结构、配置、编译和移植

目录 一、uboot源码结构 1.1 uboot源码获取 1.2 uboot的特点 1.3 uboot源码结构 二、uboot配置与编译 2.1uboot配置 2.2 uboot编译 三、uboot移植 3.1添加board信息 3.2再次配置和编译 3.3添加三星加密引导程序 3.4添加调制代码&#xff08;点灯法&#xff09; 3.…

CMMI-结项管理

结项管理&#xff08;ProjectClosing Management, PCM&#xff09;是指在项目开发工作结束后&#xff0c;对项目的有形资产和无形资产进行清算&#xff1b;对项目进行综合评估&#xff1b;总结经验教训等。结项管理过程域是SPP模型的重要组成部分。本规范阐述了结项管理的规程&…

绘图软件推荐——Diagram Designer

目录 Diagram Designer安装 软件下载 软件图标 Diagram Designer应用 新建页面 工具栏简介 绘制多边形 创建并添加图形模板 图像导出 Diagram Designer安装 软件下载 在腾讯管家&#xff0c;软件管理中 &#xff0c;搜索 Diagram Designer 即可下载软件图标 Diagram Des…

lio-sam学习笔记(三)

前言&#xff1a; 对于lio-sam前端中图像投影和特征提取部分的学习。 一、imageProjection.cpp main函数&#xff1a; int main(int argc, char** argv) {ros::init(argc, argv, "lio_sam");ImageProjection IP;ROS_INFO("\033[1;32m----> Image Project…

训练营day17

110.平衡二叉树 力扣题目链接 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 示例 1: 给定二叉树 [3,9,20,null,null,15,7] 返回 true 。 示…

GIS矢量图形多边形地块行政区发光,阴影发光特效实现

先来看下效果: 其实做到发光效果我们必须明白两件事: 1.必须有亮色作为发光色 2.必须有暗色作为衬托色 二者缺一不可 如果你仅仅用了亮色,那么效果是这样的: 注意哦,我使用的是同一个颜色哦,为什么这一次看起来就不是发光呢? 原因很简单,第二幅图我没有加衬托色 ,…

Java基础常见面试题(一)

基础概念与常识 Java 语言有哪些特点? 简单易学&#xff1b;面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff1b;平台无关性&#xff0c;平台无关性的具体表现在于&#xff0c;Java 是“一次编写&#xff0c;到处运行&#xff08;Write Once&…

手把手教你将Eureka升级Nacos注册中心

由于原有SpringCloud体系版本比较老&#xff0c;最初的注册中心使用的Eureka后期官方无升级方案&#xff0c;配置中心无法在线管理配置&#xff0c;还有实时上下线的问题&#xff0c;因此需要将原有系统的Eureka服务升级Nacos注册心服务。原有版本SpringBoot1.5.15、SpringClou…

Python序列类型之集合

&#x1f490;&#x1f490;&#x1f490;欢迎来到小十一的博客&#xff01;&#xff01;&#xff01; &#x1f3af;博客主页&#xff1a;&#x1f3af;程序员小十一的博客 &#x1f680;博客专栏&#xff1a;&#x1f680;Python入门基础语法 &#x1f337;欢迎关注&#xff…

github报错Key is invalid. You must supply a key in OpenSSH public key format

原因&#xff1a;由于github官方提示 普通类型的ssh不安全&#xff0c;所以改成OpenSSH 解决办法 第一步&#xff1a;打开终端。粘贴下面的文本&#xff0c;替换为您的 GitHub 电子邮件地址。连续按回车键 ssh-keygen -t ed25519 -C "your_emailexample.com"第二步…

JavaWeb入门看这一篇文章就够了

第一章 JavaWeb简介 第1节 什么是web 1web&#xff08;World Wide Web&#xff09;即全球广域网&#xff0c;也称为万维网&#xff0c;它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。是建立在Internet上的一种网络服务&#xff0c;为浏览者…

插入排序基本概念

插入排序基本概念1.插入排序1.1 基本概念1.2 插入排序执行步骤有1.3 对于5个元素的值步骤次数1.4 插入排序大O记法表示2. 将[4,2,7,1,3]进行插入排序 【实战】2.1 第一次轮回步骤2.2 第二次轮回步骤2.3 第三次轮回步骤2.4 第四次轮回步骤3.插入排序代码实现1.插入排序 1.1 基本…

VHDL语言基础-组合逻辑电路-译码器

目录 译码器的设计&#xff1a; 译码器的分类&#xff1a; 常用译码器&#xff1a; 3-8译码器&#xff1a; 3-8译码器的描述&#xff1a; 小结&#xff1a; 译码器的设计&#xff1a; 译码器和编码器是数字系统中广泛使用的多输入多输出组合逻辑部件。 实现译码的组合逻…

锁与原子操作

锁与原子操作 锁 以自增操作为例子&#xff1a; void *func(void *arg) {int *pcount (int *)arg;int i 0;//while (i < 100000) {(*pcount) ; // 并不会到达100000usleep(1);} }int main(){int i 0;for (i 0;i < THREAD_COUNT;i ) {pthread_create(&thid…

2023年,云计算还有发展前景吗?

云计算在促进经济回暖中扮演者不可或缺的角色&#xff0c;疫情期间复工复产都是基于云计算的基础设施&#xff0c;实现远程办公、在线学习、在线看病、在线政务等等。同时由于数字技术在各个领域的渗透和发展&#xff0c;社会整体对于云技术人才、云服务、算力服务等的需求都在…

虹科分享 | 作为域名系统的SPoF

“SPoF”或“单点故障”背后的思想是&#xff0c;如果系统的一部分发生故障&#xff0c;那么整个系统也会发生故障。 这是不可取的。在IT和安全领域&#xff0c;如果一个组件或子组件的故障会导致系统或应用程序严重中断或降级&#xff0c;那么我们通常认为设计有缺陷。 这就…

OpenAI GPT3 + Flask 利用 text-davinci-003 API 制作自己的交互网页教程 | 附源码 和 Github链接

1. OpenAI GPT3 text-davinci-003 API 最近ChatGPT很火&#xff0c;使用与InstructGPT相同的方法&#xff0c;使用来自人类反馈的强化学习 Reinforcement Learning from Human Feedback (RLHF) 来训练该模型&#xff0c;但数据收集设置略有不同。ChatGPT是在 GPT-3.5 系列中的…