26-LINUX--I/O复用-select

一.I/O复用概述

        /O复用使得多个程序能够同时监听多个文件描述符,对提高程序的性能有很大帮助。以下情况适用于I/O复用技术:

TCP 服务器同时要处理监听套接字和连接套接字。
服务器要同时处理 TCP 请求和 UDP 请求。
程序要同时处理多个套接字。
客户端程序要同时处理用户输入和网络连接。
服务器要同时监听多个端口
        需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当
多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一
个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以
配合使用多线程或多进程等编程方法

二.select机制

1.select接口介绍

        select 系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符的可读、
可写和异常等事件。
        select 系统调用的原型如下:
 #include <sys/select.h>

 int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct ti
meval *timeout);
 /*
 select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内
没有任何文件描述符就绪,select 将返回 0。select 失败是返回-1.如果在 select 等待
期间,程序接收到信号,则 select 立即返回-1,并设置 errno 为 EINTR。

 maxfd 参数指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所
有文件描述符中的最大值+1
 readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件
描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件
描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪
fd_set 结构如下:
 #define __FD_SETSIZE 1024
 typedef long int __fd_mask;
 #define __NFDBITS (8 * (int) sizeof (__fd_mask))
 typedef struct
 {
 #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;
 通过下列宏可以访问 fd_set 结构中的位:
 FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位
 FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd

 FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd
 int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置
 timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指
 针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。timeval
 结构的定义如下:
 struct timeval
 {
 long tv_sec; //秒数
 long tv_usec; // 微秒数
 };//struct timeval tv = {5,0};
 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递
NULL,则 select 将一直阻塞,直到某个文件描述符就绪
 */

2.设计思路图解

3.测试代码

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

#define STDIN 0
int main()
{
        int fd = STDIN;//键盘
        fd_set fdset;//集合,收集描述符

        while(1)//因为不止检测一次
        {
                FD_ZERO(&fdset);//清空集合,每个位置0:FD_ZERO
                FD_SET(fd,&fdset);//将描述符fd添加到集合fdset

                struct timeval tv = {5,0};//超时时间

                int n = select(fd+1,&fdset,NULL,NULL,&tv);//可能阻塞
                if(n ==-1)//select执行失败
                {
                        printf("select err\n");
                }
                else if(n==0)//超市,没有找到可用事件描述符
                {
                        printf("tme out\n");
                }
                else
                {
                        if(FD_ISSET(fd,&fdset))
                        {
                                char buff[128]={0};
                                int num = read(fd,buff,127);
                                printf("num=%d,buff=%s\n",num,buff);
                        }
                }

        }
}
~                                                                                                                                                                                            
~                                                                                                                                                                                            
~                         

4.tcp通过select实现并发连接

SER.C

#include<stdio.h>      // 标准输入输出库
#include<stdlib.h>     // 标准库,提供一些函数如malloc, free, rand等
#include<string.h>    // 字符串操作库
#include<unistd.h>    // UNIX标准函数库
#include<sys/select.h>// 选择库,提供select函数
#include<time.h>      // 时间库
#include<sys/socket.h>// 套接字库
#include<arpa/inet.h> // 提供inet_addr等函数
#include<netinet/in.h>// 提供一些网络相关的宏

#define MAXFD 10       // 定义最大文件描述符数量

// 初始化socket函数
int socket_init();

// 初始化文件描述符数组
void fds_init(int fds[]){
    for(int i=0; i<MAXFD; i++){
        fds[i] = -1; // 将所有文件描述符初始化为-1,表示未被使用
    }
}

// 将新的文件描述符添加到数组中
void fds_add(int fds[], int fd){
    for(int i=0; i<MAXFD; i++){
        if(fds[i] == -1){ // 找到数组中第一个未使用的文件描述符位置
            fds[i] = fd;  // 添加文件描述符
            break;        // 退出循环
        }
    }
}

// 从未使用的文件描述符数组中删除指定的文件描述符
void fds_del(int fds[], int fd){
    for(int i=0; i<MAXFD; i++){
        if(fds[i] == fd){ // 找到要删除的文件描述符
            fds[i] = -1;   // 将其设置为-1,表示未使用
            break;         // 退出循环
        }
    }
}

// 接受客户端连接请求并添加到文件描述符数组
void accept_client(int sockfd, int fds[]){
    int c = accept(sockfd, NULL, NULL); // 接受连接
    if(c < 0){
        return; // 如果返回-1,表示出错
    }
    printf("accept c = %d\n", c);
    fds_add(fds, c); // 添加到文件描述符数组
}

// 接收客户端发送的数据
void recv_date(int c, int fds[]){
    char buff[128] = {0}; // 创建缓冲区
    int n = recv(c, buff, 127, 0); // 接收数据
    if(n < 0){
        printf("cli close\n");
        close(c);          // 如果接收失败,关闭连接
        fds_del(fds, c);   // 从数组中删除该文件描述符
        return;
    }
    if(n == 0){
        printf("time out(%d)\n", n); // 如果超时
    }

    printf("buff(c=%d)=%s\n", c, buff); // 打印接收到的数据
    send(c, "ok", 2, 0);             // 发送确认消息
}

// 主函数
int main(){
    int sockfd = socket_init(); // 初始化socket
    if(sockfd == -1){
        exit(1); // 如果初始化失败,退出程序
    }

    int fds[MAXFD]; // 文件描述符数组
    fds_init(fds);  // 初始化数组
    fds_add(fds, sockfd); // 将监听的socket添加到数组

    fd_set fdset; // 创建文件描述符集合
    while(1){ // 无限循环等待事件
        FD_ZERO(&fdset); // 清空文件描述符集合
        int maxfd = -1; // 存储最大的文件描述符

        // 遍历文件描述符数组,将所有文件描述符添加到集合中
        for(int i=0; i<MAXFD; i++){
            if(fds[i] == -1){
                continue; // 如果文件描述符未使用,跳过
            }
            FD_SET(fds[i], &fdset); // 添加到集合
            if(fds[i] > maxfd){ // 更新最大文件描述符
                maxfd = fds[i];
            }
        }
        struct timeval tv = {5,0}; // 设置超时时间

        // 使用select等待直到有文件描述符准备好IO操作或超时
        int n = select(maxfd+1, &fdset, NULL, NULL, &tv);
        if(n == -1){
            printf("select err\n"); // 错误
        } else if(n == 0){
            printf("time out\n"); // 超时
        } else{
            // 遍历文件描述符数组,检查哪些文件描述符准备好了IO操作
            for(int i=0; i<MAXFD; i++){
                if(fds[i] == -1){
                    continue; // 如果文件描述符未使用,跳过
                }
                if(FD_ISSET(fds[i], &fdset)){ // 检查文件描述符是否被设置
                    if(fds[i] == sockfd){ // 如果是监听的socket
                        accept_client(sockfd, fds); // 接受新的连接
                    } else{ // 如果是已连接的客户端
                        recv_date(fds[i], fds); // 接收数据
                    }
                }
            }
        }
    }
}

// 创建socket并绑定到端口
int socket_init(){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建socket
    if(sockfd == -1){
        return -1; // 创建失败返回-1
    }
    struct sockaddr_in saddr; // 服务器地址结构
    memset(&saddr, 0, sizeof(saddr)); // 清零
    saddr.sin_family = AF_INET; // 地址族
    saddr.sin_port = htons(6000); // 端口
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址

    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定
    if(res == -1){
        printf("bind err\n");
        return -1; // 绑定失败返回-1
    }
    if(listen(sockfd, 5) == -1){ // 开始监听,设置队列长度为5
        return -1; // 监听失败返回-1
    }
    return sockfd; // 返回socket文件描述符
}
                 

CLI.C

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                exit(1);
        }
        struct sockaddr_in saddr;//代表服务器的端口
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(6000);
        saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                printf("connct err\n");
                exit(1);
        }

        while(1)
        {
                printf("input: ");
                char buff[128]={0};
                fgets(buff,128,stdin);
                if(strncmp(buff,"end",3)==0)
                {
                        break;
                }
                send(sockfd,buff,strlen(buff)-1,0);
                memset(buff,0,128);
                recv(sockfd,buff,127,0);
                printf("buff=%s\n",buff);
        }
        close(sockfd);
        exit(0);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用SeaFile文件共享服务器结合内网穿透将家中电脑变成个人云盘

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 本文主要为大家介绍&#xff0c;如何使用两个…

冯喜运:6.7今日外汇黄金原油走势分析及日内操作策略

【黄金消息面分析】&#xff1a;美国初请失业金人数超预期&#xff0c;市场对美联储9月降息预期升温&#xff0c;全球降息潮起&#xff0c;黄金市场受支撑。北京时间本周四&#xff0c;美国劳工部公布的数据显示&#xff0c;截至6月1日当周初请失业金人数增加至22.9万人&#x…

StableDiffusion Windows本地部署

检查电脑环境 启动CMD命令窗。 如上图&#xff0c;在CMD窗口输入python命令&#xff0c;可查看本地安装的python版本信息等。输入exit()退出python命令行 执行where命令&#xff0c;可查看python安装目录。 必须安装Python3.10.x&#xff0c;因为stable-diffusion-webui的一…

卫星通信频段有哪些

卫星通信使用到的频段涵盖L, S, C, Ku, Ka等&#xff0c;而最常用的频段是C(4~8GHz)和Ku(12~18GHz)频段&#xff0c;而Ka(27-40GHz)频段是后起之秀。目前地球赤道上空有限的地球同步卫星轨位几乎已被各国占满&#xff0c;C和Ku频段内的频率资源被大量使用&#xff0c;而Ka频段的…

Java学习中,如何理解注解的概念及常用注解的使用方法

一、简介 Java注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;提供了一种将数据与程序元素&#xff08;类、方法、字段等&#xff09;关联的方法。注解本身不改变程序的执行逻辑&#xff0c;但可以通过工具或框架进行处理&#xff0c;从而影响编译、运行时的…

新品发布 | 飞凌嵌入式RK3576核心板,为AIoT应用赋能

为了充分满足AIoT市场对高性能、高算力和低功耗主控日益增长的需求&#xff0c;飞凌嵌入式全新推出基于Rockchip RK3576处理器开发设计的FET3576-C核心板&#xff01; 集成4个ARM Cortex-A72和4个ARM Cortex-A53高性能核&#xff0c;内置6TOPS超强算力NPU&#xff0c;为您的AI…

ComfyUI 完全入门:必备插件

ComfyUI 是一个基于 Stable Diffusion 的AI绘画创作工具&#xff0c;最近发展势头特别迅猛&#xff0c;但是 ComfyUI 的上手门槛有点高&#xff0c;用户需要对 Stable Diffusion 以及各种数字技术的原理有一定的了解才行。这个系列将会介绍 ComfyUI 的一些基础概念和使用方法&a…

1+x(Java)中级题库易混淆理论题(三)

SQL 语句中进行 group by 分组时&#xff0c;可以不写 where 子句 分组时可以多层分组&#xff0c;比如&#xff1a;先按照省、再按照市来分组。 File 类不能获取文件的内容 在使用 select 语句进行查询分组时&#xff0c;如果希望去掉不满足条件的分组&#xff0c;使用 hav…

ES 8的向量检索性能调优实践

前言 ES的官方实验室曾发布过一篇博客,介绍了使ES向量检索性能获得显著提升的技术要点与展望: 多线程搜索能力的利用:Lucene 的分段架构允许实现多线程搜索能力。Elasticsearch 通过同时搜索多个段来提高性能,使用所有可用的 CPU 核心的计算能力显著减少了单个搜索的延迟。…

Laravel框架进阶:掌握队列系统,优化应用性能

Laravel使用队列处理 本文主要讲述如何利用 Laravel 框架的队列系统来管理异步任务和设置周期性执行的任务&#xff0c;从而增强应用程序的效能和可靠性。 Laravel队列的优势 异步执行&#xff1a;将任务添加到队列中后&#xff0c;可以立即返回响应给用户&#xff0c;而任务…

1000Base-T协议解读

一、说明 千兆以太网家族包括1000Base-SX(短距)、1000Base-LX(长距)、1000Base-CX(铜缆短距)、1000Base-T1(车载以太网)和1000Base-T等多种标准,我们这边主要了解下1000Base-T,也就是工业千兆以太网,PC电脑的网口都是这个。 1000Base-T采用了4D-PAM5编码技术(4D代…

SpringBoot整合RabbitMQ (持续更新中)

RabbitMQ 官网地址&#xff1a;RabbitMQ: One broker to queue them all | RabbitMQ RabbitMQ 与 Erlang 版本兼容关系​ 3.13.0 26.0 26.2.x The 3.13 release series is compatible with Erlang 26. OpenSSL 3 support in Erlang is considered to be mature and ready for…

告别冗长代码:Java Lambda 表达式如何简化你的编程

在现代软件开发中&#xff0c;高效和简洁的代码变得越来越重要。Java作为一门成熟而广泛使用的编程语言&#xff0c;一直在不断进化&#xff0c;以满足开发者的需求。Java 8的推出标志着一次重要的飞跃&#xff0c;其中最引人注目的特性之一便是Lambda表达式。 Lambda表达式为J…

Docker 进入指定容器内部(以Mysql为例)

文章目录 一、启动容器二、查看容器是否启动三、进入容器内部 一、启动容器 这个就不多说了 直接docker run… 二、查看容器是否启动 查看正在运行的容器 docker ps查看所有的容器 docker ps -a结果如下图所示&#xff1a; 三、进入容器内部 通过CONTAINER ID进入到容器…

linux命令别名与shell函数

# 修改网卡配置 alias vinetwork"vi /etc/sysconfig/network-scripts/ifcfg-ens33" 1. 方法和调用在同一个文件 # 定义shell函数,返回值通过$?获取 function say_hello(){ echo "hello shell" return 1 } # 使用shell函数 say_hello # 执行脚本后接收返…

zabbix-agent如何版本回退降低?

文章目录 1&#xff0c;查看zabbix-agent版本号2&#xff0c;查看zabbix-server的版本号3&#xff0c;卸载已有的zabbix-agent4&#xff0c;找到与zabbix-server匹配版本的zabbix-agent5&#xff0c;安装zabbix-agent 5.0.42版本6&#xff0c;查看已安装的zabbix-agent的版本号…

4秒惊艳!Stable Cascade AI绘画神器,设计师和普通用户的无限创意新选择

近日&#xff0c;一款AI绘画模型Stable Cascade发布。 只需输入一段描述文字&#xff0c;即可在4秒钟内获得令人惊艳的图像。 无论你是设计师、艺术家&#xff0c;还是普通用户&#xff0c;都能轻松上手&#xff0c;释放无限创意。 Stable Cascade不仅在使用上极具便捷性&am…

hot100_62不同路径

不同路径 题目思路、代码1.排列组合2.动态规划 题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff0…

如何提升推广链接辨识度与可信度?试试自定义链接后缀

各位大佬&#xff0c;咱今天来聊聊短信营销这个事儿。这可是好多企业都在用的营销手段啊&#xff0c;一条几分钱的短信&#xff0c;就能搭起用户和企业的桥梁&#xff0c;能增强粘性、促成交易或者推动复购&#xff0c;那真是高覆盖、低成本、高效率。 但现在问题来了&#xf…

电商数据采集决策智慧:深度解析数据采集与应用||电商API数据采集接口的接入与应用

引言 在数字化时代&#xff0c;数据已成为电商企业最宝贵的资产之一。通过有效的数据采集&#xff0c;企业能够洞察市场动态、理解消费者需求、优化运营策略&#xff0c;从而在激烈的市场竞争中脱颖而出。本文将深入探讨电商数据采集的重要性、常用方法以及应用实践。 一、电…