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

news2024/11/21 22:12:10

一.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);
}

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

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

相关文章

工业无线通信解决方案,企业在进行智能化升级改造

某大型制造企业在进行智能化升级改造,需要将分布在各个车间的数控机床、自动化生产线、AGV小车等设备连接到云端,实现设备的远程监控、数据采集分析等功能。之前工厂内部是用工业以太网连接,存在布线难、成本高、灵活性差等问题。 在了解客户需求后,我司星创易联的工程师建议客…

png怎么变成jpg?教你3种方法一键批量转换

png怎么变成jpg&#xff1f;PNG转JPG在图像处理中扮演着重要的角色。除了能够显著减小文件大小&#xff0c;使图像更易于分享和传输外&#xff0c;这种转换还能确保图像在各种平台和设备上都能得到良好的展示效果。无论是网站加载速度的优化&#xff0c;还是移动设备上的流畅浏…

教师常用小程序分享

和大家分享几款超级实用的教学小程序&#xff0c;不仅能够提高我们的办公效率&#xff0c;还能让我们的教学生活变得更加轻松有趣。 腾讯文档&#xff1a;云端协作的利器 教学协作是必不可少的。腾讯文档小程序&#xff0c;就是云端协作的利器。支持多人在线编辑文档&#xff…

在Obsidian中插入目录的解决方案

常见的方案有floating toc插件。但是这个插件与另一个用于给导出pdf添加书签的插件Better Export PDF不兼容。 因此推荐另一个解决方案&#xff1a;Dynamic Table of Contents 此插件没有上架到社区插件市场&#xff0c;需要手动去github下载安装 GitHub - Aidurber/obsidia…

Stable diffusion采样器详解

在我们使用SD web UI的过程中&#xff0c;有很多采样器可以选择&#xff0c;那么什么是采样器&#xff1f;它们是如何工作的&#xff1f;它们之间有什么区别&#xff1f;你应该使用哪一个&#xff1f;这篇文章将会给你想要的答案。 什么是采样&#xff1f; Stable Diffusion模…

加密软件好用的是哪个?为什么这么多人说迅软DSE加密软件好用?

加密软件顾名思义就是用来对机密文件进行加密保护的&#xff0c;防止未经授权的人查看和篡改&#xff0c;保护公司的重要信息&#xff0c;预防泄露的事件发生&#xff0c;由此可见运用加密软件是有用的。那么&#xff0c;问题又来了哪款加密软件好呢&#xff1f;请看一下介绍。…

MySQL学习十:常用函数与常见题型总结(持续更新中)

目录 一、常用函数1.1 left 和 right 函数1.2 first_value 函数1.3 timestampdiff 函数1.4 datediff 函数1.4 date_sub 和 date_add 函数 二、常见题型2.1 查询新登录用户总体的次日留存率&#xff08;第一天新登录的总体用户&#xff0c;第二天再次登录的总体用户&#xff09;…

Docker部署深度学习模型

基础概念 Docker Docker是一个打包、分发和运行应用程序的平台&#xff0c;允许将你的应用程序和应用程序所依赖的整个环境打包在一起。比如我有一个目标检测的项目&#xff0c;我想分享给朋友&#xff0c;那么他首先需要在自己的电脑上配置好显卡驱动、CUDA、CuDNN&#xff…

NC56 入库失败提示负库存解决方法

前言 公司的 NC ERP 接入了第三方系统进行出入库单据管理。用户反馈提交入库单据时、NC ERP 报错【负库存或辅数量方向不一致】。于是进行排查和解决。 操作环境 NC ERP V56 。操作系统 Windows 11 &#xff0c;数据库 Oracle DB 。 操作步骤 1、查询 NC “收发存汇总表”…

前端 | 燃尽图绘制

文章目录 &#x1f4da;实现效果&#x1f4da;模块实现解析&#x1f407;html&#x1f407;css&#x1f407;javascript &#x1f4da;实现效果 &#x1f4da;模块实现解析 &#x1f407;html 搭框架<div id"LINE2"><div id"cloudtitle">TED…

抖音电商课程(持续更新...)

01 客户成交方式 1 直播间载体 冲动消费&#xff08;感性&#xff09; 团队配合好&#xff0c;主攻直播间。 客单价越高&#xff0c;对直播间的依赖性越强。进直播间建立强依赖关系。 2 短视频 / 图文载体 用户处于理性和感性之间。 擅长拍摄、擅长内容输出&#xff0c;…

搭建电商项目||购物商城||APP|小程序|电商独立站系统如何接入JD商品

京东商品采集的步骤和应用场景可以归纳如下&#xff1a; 一、采集步骤 注册账号&#xff1a;首先&#xff0c;需要在京东开放平台注册一个开发者账号。创建应用&#xff1a;登录开放平台后&#xff0c;创建一个应用以获取API密钥和应用凭据。获取权限&#xff1a;根据所需的服…

如何实现网站HTTPS访问

在当今网络安全至关重要的时代&#xff0c;HTTPS已经成为网站安全的基本标准。HTTPS&#xff08;超文本传输安全协议&#xff09;通过在HTTP协议基础上加入SSL加密层&#xff0c;确保了数据在用户浏览器和服务器之间的传输是加密的&#xff0c;有效防止数据被窃取或篡改&#x…

【画板案例-线宽 Objective-C语言】

一、接下来,我们来说这个,线宽, 1.示例程序里边,这个slider,是改变线宽的,在最左边的时候,我画一下, 是这种线宽,还是有一定宽度的啊,跟我们这个默认的,不是1像素, 然后,最右边呢,也是有一个宽度的, 然后呢,这个线宽,我就给它,最小值设置成5,最大值设置成3…

面试题react03

React事件机制&#xff1a; React的事件机制可以分为两个部分&#xff1a;事件的触发和事件的处理。事件的触发&#xff1a;在React中&#xff0c;事件可以通过用户与组件进行交互而触发&#xff0c;如点击、鼠标移动、键盘输入等。当用户与组件进行交互时&#xff0c;浏览器会…

服务器数据恢复—强制上线raid5阵列离线硬盘导致raid不可用的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌2850服务器中有一组由6块SCSI硬盘组建的raid5磁盘阵列&#xff0c;linux操作系统ext3文件系统。 服务器故障&#xff1a; 服务器运行过程中突然瘫痪。服务器管理员检查阵列后发现raid5阵列中有两块硬盘离线&#xff0c;将其中一块硬盘进行…

底层穿透海银财富爆雷

吃瓜&#xff01;海银财富爆雷了&#xff0c;底层资产绝大多数子虚乌有&#xff0c;开设了N个影子公司&#xff0c;搞了规模超700亿元的“嵌套资金池”……让我们在“图”中穿透里面的故事和事故。 海银财富共计发行了465只产品&#xff0c;募集规模超过700亿元&#xff0c;我…

记录项目使用ts时引入js文件后导致项目运行空白问题

主要原因&#xff1a; 使用ts后开启了eslint检测&#xff0c;而js压缩文件引入的位置在eslint检测的文件内。导致eslint检测认为该文件为很大的文件&#xff0c;或eslint认为此文件内存在无法处理的语法结构等问题。 解决方法&#xff1a; 1、把文件移到eslint检测外的文件引入…

牛啊后续:如何一行C#代码实现解析类型的Summary注释(可用于数据字典快速生成)...

前言&#xff1a;下午有小伙伴要求&#xff0c;让我继续做个解析实体类注释信息的内容。所以我也顺便加入进来。以下开始正文实战操作&#xff1a; 项目需要勾选输出api文档文件。这样就可以让所有实体类的summary信息被写入到输出目录下。如果有多个xml文件也没关系&#xff0…

大厂AI团战高考作文,华师一附中特级教师这样打分

在人工智能的浪潮中&#xff0c; 人们不禁疑问&#xff1a; AI真的能超越人类吗&#xff1f; 这究竟是现实还是幻想&#xff1f; 我们将目睹一场前所未有的较量&#xff1a; 百度文心一言、阿里通义千问、 腾讯混元、字节豆包 四家国内顶尖互联网企业 精心打造的AI大模…