【网络编程】select

news2024/12/24 8:35:18

主旨思想

  1. 首先要构造一个关于文件描述符的列表将要监听的文件描述符添加到该列表中
     
  2. 调用一个系统函数(select),监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回
    • 这个函数是阻塞
    • 函数对文件描述符的检测的操作是由内核完成的
  3. 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作

函数说明

  • 概览

    #include <sys/time.h> 
    #include <sys/types.h> 
    #include <unistd.h> 
    #include <sys/select.h> 
    
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
    // 将参数文件描述符fd对应的标志位设置为0 
    void FD_CLR(int fd, fd_set *set); 
    // 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1 
    int FD_ISSET(int fd, fd_set *set); 
    // 将参数文件描述符fd 对应的标志位,设置为1 
    void FD_SET(int fd, fd_set *set);
    // fd_set一共有1024 bit, 全部初始化为0 
    void FD_ZERO(fd_set *set);
  • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    • 通过man select查看帮助

    • 参数

      • nfds :  委托内核检测的最大文件描述符的值 + 1(+1是因为遍历是下标从0开始,for循环<设定)
      • readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
        • 一般检测读操作
        • 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区
        • 是一个传入传出参数
      • writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
        • 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)
      • exceptfds :检测发生异常的文件描述符的集合,一般不用
      • timeout:设置的超时时间 (具体见下方select参数列表说明)
        • NULL : 永久阻塞,直到检测到了文件描述符有变化
        • tv_sec = tv_usec = 0, 不阻塞
        • tv_sec > 0,tv_usec > 0:阻塞对应的时间
    • 返回值

      • -1:失败
      • >0(n):检测的集合中有n个文件描述符发生了变化
  • select参数列表说明

    • fd_set:是一块固定大小的缓冲区(结构体), sizeof(fd_set)=128,即对应1024个比特位

    • timeval:结构体类型

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

      工作过程分析

    • 初始设定

  •   设置监听文件描述符,将fd_set集合相应位置为1

  • 调用select委托内核检测

  • 内核检测完毕后,返回给用户态结果

代码实现

  • select中需要的监听集合需要两个
        一个是用户态真正需要监听的集合rSet
         一个是内核态返回给用户态的修改集合tmpSet
  • 需要先判断监听文件描述符是否发生改变
        如果改变了,说明有客户端连接,此时需要将新的连接文件描述符加入到rSet,并更新最大文件描述符
        如果没有改变,说明没有客户端连接
  • 由于select无法确切知道哪些文件描述符发生了改变,所以需要执行遍历操作,使用FD_ISSET判断是否发生了改变
  • 如果客户端断开了连接,需要从rSet中清除需要监听的文件描述符
  • 程序存在的问题:中间的一些断开连接后,最大文件描述符怎么更新?
    ==估计不更新,每次都会遍历到之前的最大值处==
     
  • 服务器端代码:
    #include <stdio.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/select.h>
    
    #define SERVERIP "127.0.0.1"
    #define PORT 6789
    
    
    int main()
    {
        // 1. 创建socket(用于监听的套接字)
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd == -1) {
            perror("socket");
            exit(-1);
        }
        // 2. 绑定
        struct sockaddr_in server_addr;
        server_addr.sin_family = PF_INET;
        // 点分十进制转换为网络字节序
        inet_pton(AF_INET, SERVERIP, &server_addr.sin_addr.s_addr);
        // 服务端也可以绑定0.0.0.0即任意地址
        // server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(PORT);
        int ret = bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (ret == -1) {
            perror("bind");
            exit(-1);
        }
        // 3. 监听
        ret = listen(listenfd, 8);
            if (ret == -1) {
            perror("listen");
            exit(-1);
        }
        // 创建读检测集合
        // rSet用于记录正在的监听集合,tmpSet用于记录在轮训过程中由内核态返回到用户态的集合
        fd_set rSet, tmpSet;
        // 清空
        FD_ZERO(&rSet);
        // 将监听文件描述符加入
        FD_SET(listenfd, &rSet);
        // 此时最大的文件描述符为监听描述符
        int maxfd = listenfd;
        // 不断循环等待客户端连接
        while (1) {
            tmpSet = rSet;
            // 使用select,设置为永久阻塞,有文件描述符变化才返回
            int num = select(maxfd + 1, &tmpSet, NULL, NULL, NULL);
            if (num == -1) {
                perror("select");
                exit(-1);
            } else if (num == 0) {
                // 当前无文件描述符有变化,执行下一次遍历
                // 在本次设置中无效(因为select被设置为永久阻塞)
                continue;
            } else {
                // 首先判断监听文件描述符是否发生改变(即是否有客户端连接)
                if (FD_ISSET(listenfd, &tmpSet)) {
                    // 4. 接收客户端连接
                    struct sockaddr_in client_addr;
                    socklen_t client_addr_len = sizeof(client_addr);
                    int connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_addr_len);
                    if (connfd == -1) {
                        perror("accept");
                        exit(-1);
                    }
                    // 输出客户端信息,IP组成至少16个字符(包含结束符)
                    char client_ip[16] = {0};
                    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, sizeof(client_ip));
                    unsigned short client_port = ntohs(client_addr.sin_port);
                    printf("ip:%s, port:%d\n", client_ip, client_port);
    
                    FD_SET(connfd, &rSet);
                    // 更新最大文件符
                    maxfd = maxfd > connfd ? maxfd : connfd;
                }
    
                // 遍历集合判断是否有变动,如果有变动,那么通信
                char recv_buf[1024] = {0};
                for (int i = listenfd + 1; i <= maxfd; i++) {
                    if (FD_ISSET(i, &tmpSet)) {
                        ret = read(i, recv_buf, sizeof(recv_buf));
                        if (ret == -1) {
                            perror("read");
                            exit(-1);
                        } else if (ret > 0) {
                            printf("recv server data : %s\n", recv_buf);
                            write(i, recv_buf, strlen(recv_buf));
                        } else {
                            // 表示客户端断开连接
                            printf("client closed...\n");
                            close(i);
                            FD_CLR(i, &rSet);
                            break;
                        }
                    }
                }
            }
        }
    
        close(listenfd);
        return 0;
    }

    客户端代码:

  • #include <stdio.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    #define SERVERIP "127.0.0.1"
    #define PORT 6789
    
    int main()
    {
        // 1. 创建socket(用于通信的套接字)
        int connfd = socket(AF_INET, SOCK_STREAM, 0);
        if (connfd == -1) {
            perror("socket");
            exit(-1);
        }
        // 2. 连接服务器端
        struct sockaddr_in server_addr;
        server_addr.sin_family = PF_INET;
        inet_pton(AF_INET, SERVERIP, &server_addr.sin_addr.s_addr);
        server_addr.sin_port = htons(PORT);
        int ret = connect(connfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (ret == -1) {
            perror("connect");
            exit(-1);
        }
        // 3. 通信
        char recv_buf[1024] = {0};
        while (1) {
            // 发送数据
            char *send_buf = "client message";
            write(connfd, send_buf, strlen(send_buf));
            // 接收数据
            ret = read(connfd, recv_buf, sizeof(recv_buf));
            if (ret == -1) {
                perror("read");
                exit(-1);
            } else if (ret > 0) {
                printf("recv server data : %s\n", recv_buf);
            } else {
                // 表示客户端断开连接
                printf("client closed...\n");
            }
            // 休眠的目的是为了更好的观察,放在此处可以解决read: Connection reset by peer问题
            sleep(1);
        }
        // 关闭连接
        close(connfd);
        return 0;
    }

    高并发优化思考

  • 问题

  • 每次都需要利用FD_ISSET轮询[0,maxfd]之间的连接状态,如果位于中间的某一个客户端断开了连接,此时不应该再去利用FD_ISSET轮训,造成资源浪费
  • 如果在处理客户端数据时,某一次read没有对数据读完,那么造成重新进行下一次时select,获取上一次未处理完的文件描述符,从0开始遍历到maxfd,对上一次的进行再一次操作,效率十分低下
  •  解决

  • 考虑到select只有1024个最大可监听数量,可以申请等量客户端数组
    初始置为-1,当有状态改变时,置为相应文件描述符
    此时再用FD_ISSET轮训时,跳过标记为-1的客户端,加快遍历速

  •  对于问题二:对读缓存区循环读,直到返回EAGAIN再处理数据

    存在问题(缺点)

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小了,默认是1024
  • fds集合不能重用,每次都需要重置

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

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

相关文章

vue 图片回显标签

第一种 <el-form-item label"打款银行回单"><image-preview :src"form.bankreceiptUrl" :width"120" :height"120"/></el-form-item>// 值为 https://t11.baidu.com/it/app106&fJPEG&fm30&fmtauto&…

一篇五分生信临床模型预测文章代码复现——Figure 10.机制及肿瘤免疫浸润(二)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…

语音合成是什么?如何进行语音合成TTS数据采集?

我们在上一篇讲到语音数据采集分为常见的两种语音数据采集类型&#xff0c;一个是语音识别数据&#xff08;ASR&#xff09;&#xff0c;另一个是语音合成&#xff08;TTS&#xff09;。这一期中&#xff0c;我们将介绍语音合成技术是什么&#xff0c;如何采集语音合成数据和制…

【有趣的设计模式】23 种设计模式详解和场景分析

前言 七大设计原则 1、单一原则&#xff1a;一个类只负责一个职责 2、开闭原则&#xff1a;对修改关闭&#xff0c;对扩展开放 3、里氏替换原则&#xff1a;不要破坏继承关系 4、接口隔离原则&#xff1a;暴露最小接口&#xff0c;避免接口过于臃肿 5、依赖倒置原则&#xff1…

完美解决ubuntu系统QtCreator无法输入中文

在Ubuntu18 上安装搜狗输入法&#xff0c;启用fcitx输入系统之后Qt Creator 无法输入中文&#xff0c;原因是缺少fcitx的支持库libfcitxplatforminputcontextplugin.so。解决办法 1 查找是否安装相关库 $ dpkg -L fcitx-frontend-qt5 | grep .so /usr/lib/x86_64-linux-gnu/qt…

语义检索系统【二】:基于无监督训练SimCSE+In-batch Negatives策略有监督训练的语义索引召回

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

//编写程序数一下 1到 100 的所有整数中出现多少个数字9

//编写程序数一下 1到 100 的所有整数中出现多少个数字9 int main() {int i;int count 0;//用来计数for (int i 1; i < 100; i) {if (i % 10 9 || i / 10 9)count;}printf("1到 100 的所有整数中出现%d个数字9\n", count);

[openCV]基于拟合中线的智能车巡线方案V1

import cv2 as cv import os import numpy as np# 遍历文件夹函数 def getFileList(dir, Filelist, extNone):"""获取文件夹及其子文件夹中文件列表输入 dir&#xff1a;文件夹根目录输入 ext: 扩展名返回&#xff1a; 文件路径列表"""newDir d…

Day10-NodeJS和NPM配置

Day10-NodeJS和NPM 一 Nodejs 1 简介 Nodejs学习中文网:https://www.nodeapp.cn/synopsis.html Nodejs的官网:https://nodejs.org/ 概念:Nodejs是JavaScript的服务端运行环境.Nodejs不是框架,也不是编程语言,就是一个运行环境. Nodejs是基于chrome V8引擎开发的一套js代码…

mac电脑访问windows共享文件夹连接不上(设置445端口)

前提&#xff1a;首先需要保证mac和windows都在同一局域网内&#xff0c;如果不在肯定是连不上的&#xff0c;就不用往下看了。 事情是这样的&#xff0c;公司入职发了mac电脑&#xff0c;但是我是window重度用户&#xff0c;在折腾mac的过程中&#xff0c;有许多文件需要从wi…

当系统接口要加入新方法时,我真后悔没有早点学学Java设计模式

假设系统中有一个接口&#xff0c;这个接口已经被10个实现类实现了&#xff0c;突然有一天&#xff0c;新的需求来了&#xff0c;其中5个实现类需要实现同一个方法。然后你就在接口中添加了这个方法的定义&#xff0c;想着一切都很完美。 当你在接口和其中5个实现类中加完这个…

java(Collection类)

文章目录 Collection接口继承树Collection接口及方法判断删除其它 Iterator(迭代器)接口迭代器的执行原理 foreach循环Collection子接口1&#xff1a;ListList接口特点List接口方法List接口主要实现类&#xff1a;ArrayListList的实现类之二&#xff1a;LinkedListList的实现类…

【python】我用python写了一个可以批量查询文章质量分的小项目(纯python、flask+html、打包成exe文件)

web 效果预览&#xff1a; 文章目录 一、API 分析1.1 质量分查询1.2 文章url获取 二、代码实现2.1 Python2.11 分步实现2.12 一步完成2.13 完整代码 2.2 python html2.21 在本地运行2.22 打打包成exe文件2.23 部署到服务器 一、API 分析 1.1 质量分查询 先去质量查询地址&a…

处理nacos、tomcat、nginx日志增长过快问题

1.nacos日志清理 修改nacos-logback.xml 将日志级别改为error级&#xff0c;减少info级日志产生量 将<maxHistory>调整为2以下&#xff0c;将 <totalSizeCap>调整为2GB左右 比如&#xff1a; [rootiZ0jlapur4hqjezy8waee0Z logs]# ll -h total 2.1G -rw-r--r-…

mysql忘记密码重置密码步骤

1.使用管理员权限打开cmd窗口&#xff0c;winr后输入cmd&#xff0c;然后按CtrlShiftEnter. 2.停止mysql服务&#xff0c;如上图net stop mysql 3.找到mysql安装目录下的my.ini文件&#xff0c;使用管理员权限打开 4.在[mysqld]下面新增一行skip-grant-tables 5.启动mysql服务…

新的恶意软件 WikiLoader 针对意大利组织

研究人员发现了一种新的恶意软件&#xff0c;名为 WikiLoader 恶意软件。之所以这样命名&#xff0c;是因为它向维基百科发出请求&#xff0c;希望得到内容中包含 "The Free "字符串的响应。 WikiLoader 恶意软件的主要目标是意大利企业及组织。 WikiLoader 是一种…

微信到底可以添加多少好友?怎么避免加人频繁?

微信作为一款用户月活跃量超过10亿的社交聊天软件&#xff0c;已经成为人们生活中不可或缺的一部分。 微信好友上限1万个 01 不知道有没有小伙伴好奇&#xff0c;微信到底可以添加多少好友&#xff1f;正好这个话题也上热搜了&#xff0c;我们就来了解一下。 有网友表示&…

Android安卓实战项目(6)---健身运动 APP实现健身运动倒计时显示提醒(源码在文末)

Android安卓实战项目&#xff08;6&#xff09;—健身运动 APP实现健身运动倒计时&#xff08;源码在文末&#x1f415;&#x1f415;&#x1f415;&#xff09; 一.项目运行介绍 【bilibili演示】 https://www.bilibili.com/video/BV1414y167WH/?share_sourcecopy_web&…

接受平庸,特别是程序员

目录 方向一&#xff1a;简述自己的感受 方向二&#xff1a;聊聊你想怎么做 方向三&#xff1a;如何调整自己的心态 虽然清楚知识需要靠时间沉淀&#xff0c;但在看到自己做不出来的题别人会做&#xff0c;自己写不出的代码别人会写时还是会感到焦虑怎么办&#xff1f; 你是…

棕榈酰四肽-7——促进皮肤自然愈合和再生

简介 棕榈酰四肽-7&#xff08;Palmitoyl Tetrapeptide-7&#xff09;可以延缓和抑制过量细胞白介素的生成&#xff0c;从而抑制一些不必要不恰当的炎症反应和糖基化损伤。在体外实验中&#xff0c;科学家们发现在细胞白介素生成时&#xff0c;“棕榈酰四肽-7诱导呈现出一种显…