【TCP/IP】利用I/O复用技术实现并发服务器 - select函数

news2024/11/25 17:51:37

目录

I/O复用技术

select函数

设置文件描述符

指定监视范围

设置超时

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


      由服务器创建多个进程来实现并发的做法有时会带来一些问题,比如:内存上的开销、CPU的大量占用等,这些因素会消耗掉服务器端有限的计算资源、进而影响程序之间的执行效率。那么,有没有方法可以在不创建额外进程的条件下实现并发呢?当然有,那就是I/O复用技术。

I/O复用技术

        I/O复用指的是通过单个线程记录一个或多个I/O流的状态,并对不同状态下的I/O流进行协调,使进程不阻塞于某个特定的I/O调用过程中,从而将有限资源最大化利用

        引用自一段情景材料,方便大家能更好地理解这个技术背后的思想:

假设你是一个机场的空管,你需要管理到你机场的所有的航线,包括进港,出港,有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。

你会怎么做?

最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机,从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。

那么问题就来了:

  • 很快你就发现空管塔里面聚集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。
  • 空管员之间需要协调,屋子里面就1, 2个人的时候还好,几十号人以后,基本上就成菜市场了。
  • 空管员经常需要更新一些公用的东西,比如起飞显示屏,比如下一个小时后的出港排期,最后你会很惊奇的发现,每个人的时间最后都花在了抢这些资源上。

现实上我们的空管同时管几十架飞机稀松平常的事情, 他们怎么做的呢?他们用这个东西:

这个东西叫 flight progress strip,每一个块代表一个航班,不同的槽代表不同的状态,然后一个空管员可以管理一组这样的块(一组航班),而他的工作,就是在航班信息有新的更新的时候,把对应的块放到不同的槽子里面。这个东西现在还没有淘汰哦,只是变成电子的了而已。是不是觉得一下子效率高了很多,一个空管塔里可以调度的航线可以是前一种方法的几倍到几十倍。 

如果你把每一个航线当成一个 Sock(I/O 流),空管当成你的服务端 Sock 管理代码的话:

  • 第一种方法就是最传统的多进程并发模型:每进来一个新的 I/O 流会分配一个新的进程管理。
  • 第二种方法就是 I/O 多路复用:单个线程,通过记录跟踪每个 I/O 流(sock)的状态,来同时管理多个 I/O 流 。 

                                                                        参考资料:IO 多路复用是什么意思? - 罗志宇

select函数

        select是I/O复用技术中比较经典且常用到的一个函数(还有poll、epoll),在使用上,需要引入<sys/select.h>和<sys/time.h>两个头文件。

#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。其余情况为返回发生事件(监视项)的文件描述符数量

/* 监视项 */
// 1.接收数据的套接字
// 2.传输数据(无阻塞)的套接字
// 3.发生异常的套接字

/* 参数含义 */
// maxfd: 监视对象文件描述符数量
// readset: 将所有关注"是否存在待读取数据"的文件描述符注册到fd_set型变量,并传递其地址值。
// writeset: 将所有关注"是否可传输无阻塞数据"的文件描述符注册到fd_set型变量,并传递其地址值。
// exceptset: 将所有关注"是否发生异常"的文件描述符注册至fd_set型变量,并传递其地址值。
// timeout: 调用 select 函数后,为防止陷入无限阻塞的状态,传递超时(time-out)消息。


        使用select函数时,一般遵循以下流程步骤:

设置文件描述符

        在调用select函数之前,我们需要声明监视事件,并用数据类型为fd_set的变量来记录监视事件下的每个文件描述符的状态。如图所示,fd_set以数组的形式记录每个文件描述符的状态:

        以图中fd0、fd2为例,值为0,代表着所指向的文件描述符不是被监视对象;反之,fd1和fd3值为1,则是被监视对象。

        在对fd_set数组操作时,有些宏可以帮助我们简化代码的编写工作,下列所示为与对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的信息,则返回"真",用于对select调用结果的验证。

        宏对应的操作含义如图所示:

指定监视范围

        即设置select函数中的第一个参数 maxfd,其值用来标记限制对监视事件中的文件描述符最大监视数。

设置超时

        当监视的文件描述符未发生变化时,select函数会导致进程发生阻塞。为了避免这种情况发生,我们可以通过设置超时(select函数中的最后一个参数timeout)来防止这种情况的发生。

        timeout的结构体定义如下:

struct timeval
{
    long tv_sec; // 秒
    long tv_usec; // 毫秒
}

//timeval.tv_sec=5,timeval.tv_usec=500; 代表设置超时等待周期为5秒500毫秒

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

io_echoserver.cpp

#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 1024

void Sender_error(char *message);

int main(int argc, char *argv[])
{
    int port, 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];

    printf("Please input the port of socket that you want to create:\n");
    scanf("%d", &port);

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
        Sender_error((char *)"Sock creation error");
    }
    else
    {
        // 注意:serv_sock初始化成功后值为0
        fd_max = serv_sock;
    }

    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(port);

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
    {
        Sender_error((char *)"Bind() error");
    }
    if (listen(serv_sock, 5) == -1)
    {
        Sender_error((char *)"Listen error");
    }
    // 对监测项的文件描述符作初始化赋0操作
    FD_ZERO(&reads);
    // 注册serv_sock套接字信息至reads变量中
    FD_SET(serv_sock, &reads);

    while (1)
    {
        // cpy_reads用来记录文件描述符变化
        cpy_reads = reads;
        // 设置超时等待周期为5s
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        // 只关注接收数据的套接字,不对传输数据、出现异常的套接字进行监视
        // fd_num用来记录发生监视事件的文件描述符数量
        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++)
        {
            // 判断cpy_reads中是否含有文件描述符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);
                    // 将客户端的套接字文件描述符信息注册至reads中
                    FD_SET(clnt_sock, &reads);
                    if (fd_max < clnt_sock)
                    {
                        // 增加监视数上限(因为有新的客户端套接字加入)
                        fd_max = clnt_sock;
                    }
                    printf("Connected client: %d \n", clnt_sock);
                }
                else
                {
                    // 接收数据
                    str_len = read(i, buf, BUF_SIZE);
                    // 无数据,则关闭对应套接字
                    if (str_len == 0)
                    {
                        // 清除reads变量中文件描述符i的信息
                        FD_CLR(i, &reads);
                        close(i);
                        printf("Closed client: %d \n", i);
                    }
                    else
                    {
                        write(i, buf, str_len);
                    }
                }
            }
        }
    }
    close(serv_sock);
    return 0;
}

void Sender_error(char *message)
{
    puts(message);
    exit(1);
}

运行结果:

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

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

相关文章

chatgpt赋能python:Python中如何更新pip版本

Python中如何更新pip版本 Python是一种非常强大的编程语言&#xff0c;它在现代编程领域中广泛使用。使用Python的好处之一是能够轻松地在其上安装和使用各种库&#xff0c;其中许多库由Python包管理器pip&#xff08;pip-installs-packages&#xff09;提供。尽管pip可以使Py…

新建的springboot 项目往往删除(1).mvn(2) .gitignore(3) HELP.md (4)mvnw (5)mvnw.cmd 文件

问题 新建的springboot 项目&#xff0c;项目开发人员往往删除&#xff08;1&#xff09;.mvn&#xff08;2&#xff09; .gitignore&#xff08;3&#xff09; HELP.md &#xff08;4&#xff09;mvnw &#xff08;5&#xff09;mvnw.cmd 文件&#xff0c;这些文件是什么&…

康耐视3D相机-DSMAX-VisionPro软件安装向导

机器视觉Halcon-字符识别 一. 系统需求 PC最小需求. OS: Win7 Pro/8Pro/10Pro X64 . Memory: 4GB RAM . PCIe 槽:X4 Gen2 一个 . 显存大小大于1GB 注意:1)PCIe插槽推荐使用x4 Gen 3; 软件版本VisionPro 9.2 CR1 X64/VisionPro 9.5及以上版本+ VisionPro_9_2_CR1_64-bit_…

合宙Air724UG Cat.1模块硬件设计指南--开关机

开关机 简介 模块支持上电后的开机、关机、复位三种状态&#xff0c;本章节会对其进行相应的介绍。 特性 PWRKEY&#xff1a; VILmin0V&#xff0c;VILmax0.5V&#xff0c;推荐值0.1V以下&#xff1b; 引脚状态&#xff1a;模块供电后PWRKEY内部拉高&#xff0c;满足低电平输入…

【哈佛积极心理学笔记】第22讲 自尊与自我实现

第22讲 自尊与自我实现 Unconditional self-esteem is the highest level, the level that Maslow would talk about “the self-actualization”, what David Schnarch talks about as “differentiated” or at the level of being known rather than desiring to be valida…

chatgpt赋能python:Python怎样使用断言?

Python怎样使用断言&#xff1f; 在Python中&#xff0c;断言是一种在程序执行中自动检查程序是否具有给定条件的方法。在程序的开发和调试过程中&#xff0c;通过正确使用断言&#xff0c;可以增强代码的可靠性并提升编程效率。 Python中的断言语法 Python中的断言语法非常…

CRC校验(2):CRC32查表法详解、代码实现及CRC反转

对于现在的CPU来说&#xff0c;基本上都在硬件上实现了CRC校验。但我们还是想用软件来实现一下CRC的代码&#xff0c;这样可以更深入地理解里面的原理。所以这一节就来详细地解释如何使用查表法从软件上来实现CRC-32的校验。另外&#xff0c;CRC还有一种反转的情况&#xff0c;…

MongoDB入门笔记

MongoDB入门笔记 1.MongoDB简介 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 它支持的数据结构非常松散…

LuatOS-Air AT应用指南--CMUX

目录 简介 语法规则 参数定义 简介 CMUX是指串口多路复用。串口的多路复用器模式&#xff0c;就是使一个串行接口能够将数据传输到四个不同的客户应用程序。 要在Linux下使用模块的CMUX功能&#xff0c;需要在内核中开启相应的支持&#xff0c;开启方法见下图 将Air724UG开…

flink学习文档四 checkpoint机制

目的 checkpoint作为flink保障任务稳健运行的一个重要机制&#xff0c;在日常使用和flink 学习框架图 简单创建一个FlinkKafkaConsumer kafka是大数据中常用的消息存储中间件&#xff0c;也是flink任务中最常用的source源之一&#xff0c;因此flink 也为 kafka提供了内置的连接…

(UE5 5.2)HISM Mobile DrawInstance在渲染层的实现浅分析

在 (UE4 4.27) UHierarchicalInstancedStaticMesh(HISM)原理分析 这篇博客大致介绍HISM组件从游戏线程到渲染线程的重建KD-Tree和剔除并提交DrawCall逻辑&#xff0c;但是没有分析渲染层的大致数据结构和实现. FHierarchicalStaticMeshSceneProxy的相关数据结构 可以看出FHier…

YOLOv5改进系列(9)——替换主干网络之EfficientNetv2

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

C++ 设计模式----组件协作型模式

面向对象设计&#xff0c;为什么&#xff1f; 回答&#xff1a;变化是复用的天敌&#xff01;面向对象设计最大的优势在于&#xff1a;抵御变化 重新认识面向对象 理解隔离变化 ​ 从宏观层面来看&#xff0c;面向对象的构建方式更能适应软件的变化&#xff0c;能将变化所…

LLMs:OpenAI 官方文档发布提高 GPT 使用效果指南—GPT最佳实践(GPT best practices)翻译与解读

LLMs&#xff1a;OpenAI 官方文档发布提高 GPT 使用效果指南—GPT最佳实践(GPT best practices)翻译与解读 导读&#xff1a;为了获得优质输出&#xff0c;需要遵循几点基本原则&#xff1a; >> 写清楚指令&#xff1a;将任务和期望输出描述得尽可能清楚。GPT 无法读取您…

NLP——Question Answering 问答模型

文章目录 2 key approachesInformation retrieval-based QAQuestion Processing 问题处理Answer Types Retrieval 文档检索Answer Extraction 答案提取 Knowledge-based QASemantic Parsing 语义解析 Hybrid QAEvaluation 2 key approaches Information retrieval-based QA 基于…

JSBridge

在Hybrid模式下&#xff0c;H5会经常需要使用Native的功能&#xff0c;比如打开二维码扫描、调用原生页面、获取用户信息等&#xff0c;同时Native也需要向Web端发送推送、更新状态等&#xff0c;而JavaScript是运行在单独的JS Context中&#xff08;Webview容器、JSCore等&…

mysql select是如何一步步执行的呢?

mysql select执行流程如图所示 server侧 在8.0之前server存在查询语句对应数据的缓存&#xff0c;不过在实际使用中比较鸡肋&#xff0c;对于更新比较频繁、稍微改点查询语句都会导致缓存无法用到 解析 解析sql语句为mysql能够直接执行的形式。通过词法分析识别表名、字段名等…

IIC总线实验

IIC总线实验 一、IIC总线基础概念 1、I2C总线是PHLIPS公司在八十年代初推出的一种同步串行半双工总线&#xff0c;主要用于连接整体电路。 2、I2C总线为两线制&#xff0c;只有两根双向信号线 3、一根是数据线SDA&#xff0c;另一根是时钟线SCL 4、I2C硬件结构简单&#xf…

第四章 模型篇:模型训练与示例

文章目录 SummaryAutogradFunctions ()GradientBackward() OptimizationOptimization loopOptimizerLearning Rate SchedulesTime-dependent schedulesPerformance-dependent schedulesTraining with MomentumAdaptive learning rates optim.lr_scheluder Summary 在pytorch_t…

一分钟学一个 Linux 命令 - find 和 grep

前言 大家好&#xff0c;我是 god23bin。欢迎来到《一分钟学一个 Linux 命令》系列&#xff0c;每天只需一分钟&#xff0c;记住一个 Linux 命令不成问题。今天需要你花两分钟时间来学习下&#xff0c;因为今天要介绍的是两个常用的搜索命令&#xff1a;find 和 grep 命令。 …