多路转接select服务器

news2025/4/23 10:55:36

目录

select函数原型

select服务器

select的缺点


前面介绍过多路转接就是能同时等待多个文件描述符,这篇文章介绍一下多路转接方案中的select的使用

select函数原型

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout);

先介绍一下各个参数以及返回值

多路转接需要等待多个文件描述符的事件就绪,所以用户势必需要告诉操作系统,他关心的是哪些文件描述符,以及关心这些文件描述符上的读事件还是写事件。读事件就绪就是这个文件描述符的缓冲区不为空有数据能读,写事件就绪就是缓冲区不为满可以写入。除了这两种常见的事件外,还可以关心某个文件描述符的异常事件。

再来看select的参数,nfds是一个整数,可以告诉操作系统需要关心哪些文件描述符,具体来说,nfds是需要关心的文件描述符的最大值 + 1,可以预想到select函数会遍历小于等于nfds - 1的文件描述符,查看是否有事件就绪

struct timeval {
    time_t      tv_sec;  /* Seconds */          //秒
    suseconds_t tv_usec; /* Microseconds */     //毫秒
};

timeout表示select的等待时间,timeout也可为空,表示阻塞等待直到某个文件描述符发生事件,timeout为0表示不等待事件发生,其他自定义值表示若在这段时间内没有事件发生,则超时返回。

返回值为0表示超时返回;为-1表示有错误发生,并设置错误码errno;为正数表示在timeout时间内事件就绪的文件描述符个数

为了介绍剩下的三个参数,先介绍一下fd_set

我们已经通过fds告诉操作系统要关心哪些文件描述符,timeout设置了等待时间,现在还需要告诉操作系统要关心哪些文件描述符的读事件或写事件

从抽象的层面上理解,fd_set是一个集合,是一个文件描述符的集合,readfds是关心读事件的文件描述符集合,writefds是关心写事件的文件描述符集合,exceptfds是关心异常事件的文件描述符集合。

还需要指出,这三个参数还是输出型参数,操作系统会将等待后事件就绪的文件描述符加入集合,

比如关心4,5,6的读事件,若就绪了4和5,集合就会变成4,5,这也为写代码带来了麻烦

从具体实现上来看,fd_set是一个位图,有若干个比特位表示文件描述符,值为1表示关心这个文件描述符,为0表示不关心,举个例子

00011111001

下标从0开始的话,这个位图表示关心3,4,5,6,7,10号文件描述符,其余的都不关心

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#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_set操作,操作系统也提供了相关的接口

// 将文件描述符fd从集合set中删除
void FD_CLR(int fd, fd_set *set);
// 判断文件描述符fd是否在集合set中
int  FD_ISSET(int fd, fd_set *set);
// 将fd放入集合set中
void FD_SET(int fd, fd_set *set);
// 清空集合set
void FD_ZERO(fd_set *set);

select服务器

到这里,select已经可以等待多个文件描述符的一些事件了,可以来搭一个简单的服务器,接收多个用户的消息,回显在屏幕上

这里只给出select_server的代码,其他文件的代码对理解select不重要,只需要了解套接字的使用便可轻松看懂,若要查看其他文件的代码,详见rokobo/wsl_code - Gitee.com

在这份代码中,需要等待的事件有等待客户端的连接和等待客户端发消息,由于连接建立后会创建文件描述符,文件描述符会变多,需要一个数据结构把这些文件描述符管理起来,这里选择了原生数组,因为可以直观的感受到select的缺点之一,存在大量遍历,性能不够高。

#include "socket.hpp"
#include "Log.hpp"
#include <sys/select.h>
#include <memory>
#include <cstring>
#include <cerrno>
using namespace SocketModule;
using namespace LogModule;

class select_server
{
// sizeof可以得到底层数组的字节数,乘8得到比特数
static const int NUM = sizeof(fd_set) * 8;
public:
    select_server()
        :_listen_sock(std::make_shared<TcpSocket>()),
        _is_running(false)
    {}

    void init(int port)
    {
        _listen_sock->BuildTcpSocketMethod(port);
        for(int i=0;i<NUM;++i)
        {
            fds[i] = -1;
        }
        //初始只需要关心_listen_sock这一个文件描述符
        fds[0] = _listen_sock->Fd();
    }

    void loop()
    {
        _is_running = true;
        int listenfd = _listen_sock->Fd();
        fd_set readset;
        while(_is_running)
        {
            //readset作为输出参数,select返回后可能被修改,需要清空后重新设置
            FD_ZERO(&readset);
            int max_fd = 0;
            for(int i=0;i<NUM;++i)
            {
                if(fds[i] != -1)
                {
                    max_fd = fds[i] > max_fd ? fds[i] : max_fd;
                    FD_SET(fds[i], &readset);
                }
            }
            struct timeval timeout = {2, 0};
            int ret = select(max_fd + 1, &readset, nullptr, nullptr, &timeout);
            if(ret == -1)
            {
                LOG(LogLevel::ERROR) << "Error message: " << strerror(ret);
                continue;
            }
            else if(ret == 0)
            {
                LOG(LogLevel::INFO) << "Time out\n";
                continue;
            }
            else
            {
                LOG(LogLevel::INFO) << "Dispatch begin\n";
                // 给不同种类的文件描述符分发不同的任务
                dispatcher(readset);
            }
        }  
    }
    void accepter(int fd)
    {
        InetAddr client;
        auto client_sock = _listen_sock->Accepter(&client);
        if(client_sock == nullptr)
        {
            LOG(LogLevel::ERROR) << "Accept error";
            return;
        }
        int client_fd = client_sock->Fd();
        if(client_fd < 0)
        {
            LOG(LogLevel::ERROR) << "Client fd error";
            return;
        }
        //将client_fd加入到fds中
        //如果fds满了,关闭连接
        int i=0;
        for(i=0;i<NUM;++i)
        {
            if(fds[i] == -1)
            {
                fds[i] = client_fd;
                LOG(LogLevel::INFO) << "Accept success: " << client_sock->Fd() << " " << client.Addr();
                break;
            }
        }
        if(i == NUM)
        {
            LOG(LogLevel::ERROR) << "Too many connections";
            client_sock->Close();
            return;
        }
    }


    void recver(int who)
    {
        int fd = fds[who];
        std::string buffer;
        auto client_sock = std::make_shared<TcpSocket>(fd);
        ssize_t ret = client_sock->Recv(&buffer);
        if(ret == -1)
        {
            LOG(LogLevel::ERROR) << "Recv error" << strerror(errno);
            client_sock->Close();
            //将fd从fds中删除
            fds[who] = -1;
            return;
        }
        else if(ret == 0)
        {
            LOG(LogLevel::INFO) << "Client closed: " << client_sock->Fd();
            client_sock->Close();
            //将fd从fds中删除
            fds[who] = -1;
            return;
        }
        else
        {
            LOG(LogLevel::INFO) << "Recv success: " << buffer;
            return;
        }
    }

    void dispatcher(fd_set &readset)
    {
        //找到所有合法的fd,分发
        for(int i=0;i<NUM;++i)
        {
            if(fds[i] == -1)
                continue;
            if(FD_ISSET(fds[i], &readset))
            {
                //分发给处理连接的函数
                if(fds[i] == _listen_sock->Fd())
                {
                    accepter(fds[i]);
                }
                //分发给处理IO的函数
                else
                {
                    recver(i);
                }
            }
        }
    }

    void stop()
    {}
private:
    std::shared_ptr<TcpSocket> _listen_sock;
    int fds[NUM];
    bool _is_running;
};

主函数

#include "select_server.hpp"
#include <string>
int main()
{
    select_server s_svr;
    s_svr.init(8080);
    s_svr.loop();

    return 0;
}

 

select的缺点

从代码中大量的遍历,甚至select底层还要遍历,可以感受到select有太多遍历,效率不高,而且fd_set的底层数组是静态的无法扩容,能同时关心的文件描述符有限,而且需要用户自己去定义数据结构管理需要关心的文件描述符,更是增加了编码的复杂性,每次调用select,都需要把fd_set从用户态拷贝到内核态,这个拷贝的开销在fd很多时开销很大

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

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

相关文章

系统架构设计师:流水线技术相关知识点、记忆卡片、多同类型练习题、答案与解析

流水线记忆要点‌ ‌公式 总时间 (n k - 1)Δt 吞吐率 TP n / 总时间 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 关键概念 周期&#xff1a;最长段Δt 冲突‌&#xff1a; ‌数据冲突&#xff08;RAW&#xff09; → 旁路/…

复刻低成本机械臂 SO-ARM100 3D 打印篇

视频讲解&#xff1a; 复刻低成本机械臂 SO-ARM100 3D 打印篇 清理了下许久不用的3D打印机&#xff0c;挤出机也裂了&#xff0c;更换了喷嘴和挤出机夹具&#xff0c;终于恢复了正常工作的状态&#xff0c;接下来还是要用起来&#xff0c;不然吃灰生锈了&#xff0c;于是乎想起…

Flutter IOS 真机 Widget 错误。Widget 安装后系统中没有

错误信息&#xff1a; SendProcessControlEvent:toPid: encountered an error: Error Domaincom.apple.dt.deviceprocesscontrolservice Code8 "Failed to show Widget com.xxx.xxx.ServerStatus error: Error DomainFBSOpenApplicationServiceErrorDomain Code1 "T…

Spring之我见 - Spring MVC重要组件和基本流程

核心组件详解 前端控制器 - DispatcherServlet 作用&#xff1a;所有请求的入口&#xff0c;负责请求分发和协调组件。 public class DispatcherServlet extends HttpServlet {// 核心服务方法protected void doService(HttpServletRequest request, HttpServletResponse re…

使用 Axios 进行 API 请求与接口封装:打造高效稳定的前端数据交互

引言 在现代前端开发中&#xff0c;与后端 API 进行数据交互是一项核心任务。Axios 作为一个基于 Promise 的 HTTP 客户端&#xff0c;以其简洁易用、功能强大的特点&#xff0c;成为了前端开发者处理 API 请求的首选工具。本文将深入探讨如何使用 Axios 进行 API 请求&#x…

理解字符设备、设备模型与子系统:以 i.MX8MP 平台为例

视频教程请关注 B 站&#xff1a;“嵌入式 Jerry” Linux 内核驱动开发中&#xff0c;很多人在接触字符设备&#xff08;char device&#xff09;、设备模型&#xff08;device model&#xff09;和各种子系统&#xff08;subsystem&#xff09;时&#xff0c;往往会感到概念混…

鸿蒙Flutter仓库停止更新?

停止更新 熟悉 Flutter 鸿蒙开发的小伙伴应该知道&#xff0c;Flutter 3.7.12 鸿蒙化 SDK 已经在开源鸿蒙社区发布快一年了&#xff0c; Flutter 3.22.x 的鸿蒙化适配一直由鸿蒙突击队仓库提供&#xff0c;最近有小伙伴反馈已经 2 个多月没有停止更新了&#xff0c;不少人以为停…

网络基础概念(下)

网络基础概念&#xff08;上&#xff09;https://blog.csdn.net/Small_entreprene/article/details/147261091?sharetypeblogdetail&sharerId147261091&sharereferPC&sharesourceSmall_entreprene&sharefrommp_from_link 网络传输的基本流程 局域网网络传输流…

一个关于相对速度的假想的故事-4

回到公式&#xff0c; 正写速度叠加和倒写速度叠加的倒写相等&#xff0c;这就是这个表达式所要表达的意思。但倒写叠加用的是减法&#xff0c;而正写叠加用的是加法。当然是这样&#xff0c;因为正写叠加要的是单位时间上完成更远的距离&#xff0c;而倒写叠加说的是单位距离需…

Idea创建项目的搭建方式

目录 一、普通Java项目 二、普通JavaWeb项目 三、maven的JavaWeb项目 四、maven的Java项目 一、普通Java项目 1. 点击 Create New Project 2. 选择Java项目&#xff0c;选择JDK&#xff0c;点击Next 3. 输入项目名称&#xff08;驼峰式命名法&#xff09;&#xff0c;可选…

【DeepSeek 学习推理】Llumnix: Dynamic Scheduling for Large Language Model Serving实验部分

6.1 实验设置 测试平台。我们使用阿里云上的16-GPU集群&#xff08;包含4个GPU虚拟机&#xff0c;类型为ecs.gn7i-c32g1.32xlarge&#xff09;。每台虚拟机配备4个NVIDIA A10&#xff08;24 GB&#xff09;GPU&#xff08;通过PCI-e 4.0连接&#xff09;、128个vCPU、752 GB内…

Kubernetes相关的名词解释kubeadm(19)

kubeadm是什么&#xff1f; kubeadm 是 Kubernetes 官方提供的一个用于快速部署和管理 Kubernetes 集群的命令行工具。它简化了集群的初始化、节点加入和升级过程&#xff0c;特别适合在生产环境或学习环境中快速搭建符合最佳实践的 Kubernetes 集群。 kubeadm 的定位 不是完整…

什么是负载均衡?NGINX是如何实现负载均衡的?

大家好&#xff0c;我是锋哥。今天分享关于【什么是负载均衡&#xff1f;NGINX是如何实现负载均衡的&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是负载均衡&#xff1f;NGINX是如何实现负载均衡的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源…

基于Python(Django)+SQLite实现(Web)校园助手

校园助手 本校园助手采用 B/S 架构。并已将其部署到服务器上。在网址上输入 db.uplei.com 即可访问。 使用说明 可使用如下账号体验&#xff1a; 学生界面: 账号1&#xff1a;123 密码1&#xff1a;123 账户2&#xff1a;201805301348 密码2&#xff1a;1 # --------------…

从零开始搭建Django博客②--Django的服务器内容搭建

本文主要在Ubuntu环境上搭建&#xff0c;为便于研究理解&#xff0c;采用SSH连接在虚拟机里的ubuntu-24.04.2-desktop系统搭建&#xff0c;当涉及一些文件操作部分便于通过桌面化进行理解&#xff0c;通过Nginx代理绑定域名&#xff0c;对外发布。 此为从零开始搭建Django博客…

【读论文】HM-RAG:分层多智能体多模态检索增强生成

如何在多模态信息检索和生成中&#xff0c;通过协作式多智能体系统来处理复杂的多模态查询。传统的单代理RAG系统在处理需要跨异构数据生态系统进行协调推理的复杂查询时存在根本性限制:处理多种查询类型、数据格式异质性和检索任务目标的多样性&#xff1b;在视觉内容和文本内…

文件操作和IO(上)

绝对路径和相对路径 文件按照层级结构进行组织&#xff08;类似于数据结构中的树型结构&#xff09;&#xff0c;将专门用来存放管理信息的特殊文件称为文件夹或目录。对于文件系统中文件的定位有两种方式&#xff0c;一种是绝对路径&#xff0c;另一种是相对路径。 绝对路径…

JavaFX深度实践:从零构建高级打地鼠游戏(含多物品与反馈机制)

大家好&#xff01;经典的“打地鼠”游戏是许多人童年的回忆&#xff0c;也是学习 GUI 编程一个非常好的切入点。但仅仅是“地鼠出来就打”未免有些单调。今天&#xff0c;我们来点不一样的——用 JavaFX 打造一个高级版的打地鼠游戏&#xff01;在这个版本中&#xff0c;洞里钻…

Python 简介与入门

目录 一、Python 初识 1、Python 的优势 2、Python 的特性 3、Python 的应用领域 二、Linux 环境中安装 Python 1、下载 Python3.11.6 2、安装依赖包 3、解压 Python 压缩包 4、安装 Python 5、编译及安装 6、建立软链接 7、测试 Python3 运行 8、设置国内 pip 更…

理解RAG第六部分:有效的检索优化

在RAG系统中&#xff0c;识别相关上下文的检索器组件的性能与语言模型在生成有效响应方面的性能同样重要&#xff0c;甚至更为重要。因此&#xff0c;一些改进RAG系统的努力将重点放在优化检索过程上。 从检索方面提高RAG系统性能的一些常见方法。通过实施高级检索技术&#x…