60 多路转接select

news/2024/10/22 14:43:37/

初识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);

参数解释:
参数nfds是需要监视的最大的文件描述符+1
rdset,wrset,exset分别对应需要检测的可读文件描述符结合,可写文件描述符结合,异常文件描述符集合
timeout为结构timeval,用来设置select()的等待时间
除过第一个参数,其他的都是输入输出型参数,需要提前设置输入值,也会返回调用后的输出值

timeval结构
描述一段时间长度,时间戳函数就是这种结构,需要监视的描述符没有事件发生函数返回,返回值为0
在这里插入图片描述
timeout取值
如果设置了,就是输入输出型参数
NULL,没有timeout,将一直没阻塞,直到某个文件描述符发生了事件
0:仅检测描述符集合的状态,然后立即返回,不等待外部事件的发生
特定的时间值:如果在指定的时间段里没有事件发生,将超时返回,有发生立即返回。如【5,0】,表示每隔5秒timeout一次返回,如果发生立即返回,返回值是剩余的时间

fd_set结构
在这里插入图片描述在这里插入图片描述

这个结构是一个整数数组,更严格的说是位图,比特位的位置表示文件描述符编号,可以关注的事件有读写异常,每个都是fd_set位图结构,值是0或1。输入时,用户告诉内核,给一个或多个fd,需要关心他们的事件,如果就绪了通知我。将对应比特位设置为1表示需要关注的fd编号。输出时,内核告诉用户,让自己关系你的多个fd中,哪些就绪了,可以读写。注定会有大量的位图操作,所以提供了一些接口

   void FD_CLR(int fd, fd_set *set);      // 用来清除描述词组set中相关fd 的位int  FD_ISSET(int fd, fd_set *set);    // 用来测试描述词组set中相关fd 的位是否为真void FD_SET(int fd, fd_set *set);      // 用来设置描述词组set中相关fd的位void FD_ZERO(fd_set *set);             // 用来清除描述词组set的全部位

函数返回值:
执行成功返回文件描述词状态已改变的个数
如果返回0表示改变前已超过timeout时间,没有返回
有错误发生返回-1,原因存于errno,测试readfds,writefds,exceptfds和timeout值不可预测

错误值可能为:
EBADF,文件描述符无效或已关闭
EINTR,此调用被信号所终端
EINVAL,参数n为负值
ENOMEM,核心内存不足

执行过程

理解select模型的关键在于理解fd_set,为了方便说明,长度取1字节,fd_set每个bit可以对应一个文件描述符fd,1字节最大可以对应8个fd

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。 *(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) *(3)若再加入fd=2,fd=1,则set变为0001,0011 *(4)执行select(6,&set,0,0,0)阻塞等待 *(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

就绪条件

读就绪
socket内核中,接收缓冲区的字节数,大于等于低水位标记SO_RCVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0
socket tcp通信中,对端关闭连接,此时对socket读,返回0
监听的socket上有新的连接请求
socket有未处理的错误

写就绪
socket内核中,发送缓冲区的可用数字(发送缓冲区的空闲位置大小),大于等于低水位标记SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0
socket的写操作被关闭(close或者shutdown),对一个写操作被关闭的socket写,会触发SIGPIPE信号
socket使用非阻塞connet连接成功或失败后
socket有未读取的错误

异常就绪
socket上收到带外数据,带外数据和tcp紧急模式相关

特点

可监控的文件描述符个数取决于sizeof(fd_set)的值,可能是512,每bit表示一个文件描述符,所以最大支持的是512*8=4096
将fd加入select监控集的同时,还要使用一个数据结构array保存放到select监控集合的fd

  • 一是用于再select返回后,array作为原数据和fd_set进行FD_INSET判断
  • 二是select返回后会把先前加入的但并无事件发生的fd情况,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得最大值maxfd,用于select第一个参数

select服务器

实现一个简单的服务器,监听套接字,运行的时候设置读位图,用select轮询,有新链接的时候处理
新连接如果不拿取会反复通知
用一个数组记录需要监听的套接字,初始值都设为-1,开始时将监听套接字加入0号下标
开始轮询,遍历数组需要设置的套接字放入fd_set,同时更新最大的套接字maxfd。select判断套接字状态,大于0时说明有事件就绪,开始处理。这时需要遍历输出参数fd_set,和套接字数组对比,哪个就绪了处理哪个。
a. 如果是监听套接字,说明有新连接,用accept函数获取新套接字,加入到套接字数组中。
b. 另一种情况是读套接字就绪,read读数据并打印,如果出错或者返回0,关闭套接字,从套接字数组中移除

SelectServer.hpp

#include "Socket.hpp"
#include "log.hpp"static const uint16_t defaultport = 8000;
static const int fd_max = (sizeof(fd_set) * 8);
int defaultfd = -1;class SelectServer
{
public:SelectServer(){for (int i = 0; i < fd_max; i++){_fd_ary[i] = defaultfd;}}bool Init(){_listensocket.Socket();int opt = 1;setsockopt(_listensocket._sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));_listensocket.Bind(defaultport);_listensocket.Listen();return true;}void Accepter(){cout << "get a new link" << endl;std::string clientip;uint16_t clinetport = 0;int sock = _listensocket.Accept(&clientip, &clinetport);if (sock < 0){return;}lg.logmessage(info, "accept success,%s:%d.%d", clientip.c_str(), clinetport, sock);// 添加sockint pos = 1;for (; pos < fd_max; pos++){if (_fd_ary[pos] != defaultfd){continue;}else{break;}}if (pos == fd_max){lg.logmessage(warning, "server is full,close %d", sock);close(sock);}else{_fd_ary[pos] = sock;// 打印查看fdaryPrintFd();}}void Recver(int fd, int pos){char buff[1024];ssize_t n = read(fd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << "get message:" << buff << endl;}else if (n == 0){lg.logmessage(info, "client quit, me too, fd:", fd);close(fd);_fd_ary[pos] = defaultfd; // 这里本质是移除}else{lg.logmessage(warning, "recv error,fd:", fd);close(fd);_fd_ary[pos] = defaultfd; // 这里本质是移除}}void Dispatcher(fd_set &rfds){// 遍历找出就绪描述符for (int i = 0; i < fd_max; i++){int fd = _fd_ary[i];if (fd == defaultfd)continue;if (FD_ISSET(fd, &rfds)){// 监听就绪if (fd == _listensocket._sockfd){Accepter();}else // 读就绪{Recver(fd, i);}}}}void Start(){// listen套接字设置第一个int listensock = _listensocket.Fd();_fd_ary[0] = listensock;for (;;){fd_set rfds;FD_ZERO(&rfds);// 取maxfdint maxfd = _fd_ary[0];for (int i = 0; i < fd_max; i++) // 第一次循环{if (_fd_ary[i] == defaultfd)continue;// 有值,设置进fd_setFD_SET(_fd_ary[i], &rfds);if (maxfd < _fd_ary[i]){maxfd = _fd_ary[i];lg.logmessage(info, "maxfd:%d", maxfd);}}// 不能直接accpet,检测listensocket的事件,新链接到来等价于读就绪struct timeval timeout = {0, 0}; // 输入输出需要重新设置int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);// 就绪不处理,会一直通知,这时读取不会阻塞switch (n){case 0:cout << "timeout," << timeout.tv_sec << "." << timeout.tv_usec << endl;break;case -1:cerr << "select error" << endl;break;default:// 有事件就绪,todoDispatcher(rfds);break;}}}void PrintFd(){cout << "online fd list: ";for (int i = 0; i < fd_max; i++){if (_fd_ary[i] == defaultfd)continue;cout << _fd_ary[i] << " ";}cout << endl;}~SelectServer(){_listensocket.Close();}private:Sock _listensocket;int _fd_ary[fd_max];
};

SelectServer.cc

#include "SelectServer.hpp"
#include <memory>int main()
{std::unique_ptr<SelectServer> svr(new SelectServer());svr->Init();svr->Start();
}

优缺点

优点
跨平台性好,几乎所有的操作系统都支持 select。
简单易用,接口简洁,易于理解和上手
缺点
输入输出型参数较多,每次调用,都需要手动设置fd集合,从接口使用角度来说非常不便
输入输出型参数较多,每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时很大
同时每次调用都需要在内核遍历传递进来的所有fd,内核中检测也需要遍历,开销在fd很多时很大
select支持的文件描述符数量太小,有上限


http://www.ppmy.cn/news/1501273.html

相关文章

Meta发布Llama 3.1开源大语言模型;谷歌发布NeuralGCM AI天气预测模型

&#x1f989; AI新闻 &#x1f680; Meta发布Llama 3.1开源大语言模型 摘要&#xff1a;Meta正式发布了开源大语言模型Llama 3.1&#xff0c;包括8B、70B和405B参数版本。Llama 3.1在推理能力和多语言支持方面有所改进&#xff0c;上下文长度提升至128K&#xff0c;405B参数…

【算法】课程表

难度:中等 题目: 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例如,先修课程…

实战机器学习--决策树分类器在蘑菇分类中的应用

数据集&#xff1a;https://pan.quark.cn/s/4d3526600c0c 在机器学习领域&#xff0c;图像分类是一个常见的任务&#xff0c;尤其是在自然语言处理和生物识别等领域。本文将通过一个简单的例子&#xff0c;展示如何使用Python和一些流行的库来实现蘑菇的分类&#xff0c;区分可…

LLM 大语言模型显存消耗估计与计算

LLM 大语言模型显存消耗估计与计算 1. LLM 大语言模型开发流程 在大模型&#xff08;如 LLaMA-7B、GPT-3 等&#xff09;的开发、训练、微调、推理和部署过程中&#xff0c;各个阶段的流程都涉及多个复杂的步骤。以下是详细的流程描述&#xff0c;涵盖训练和微调的区别&#…

Unity学习笔记之Inspector窗口可编辑的变量

笔记&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public enum TypeEnum {Normal,Player } [System.Serializable] public struct MyStruct {public int ages;public bool sex; } [System.Serializable] public class MyClas…

【React】通过实际示例详解评论列表渲染和删除

文章目录 一、引言二、初始状态与状态更新1. 使用useState钩子管理状态2. 评论列表的初始数据 三、列表渲染的实现1. list.map(item > { ... })2. return 语句3. JSX 语法4. 为什么这样设计5. 完整解读 四、列表项的唯一标识1. key 的作用2. key 的用法3. 可以没有 key 吗&a…

Postman中的数据驱动测试:API测试数据准备全攻略

Postman中的数据驱动测试&#xff1a;API测试数据准备全攻略 在API测试中&#xff0c;数据准备是确保测试覆盖全面性和准确性的关键步骤。Postman&#xff0c;作为业界领先的API开发工具&#xff0c;提供了强大的数据准备功能&#xff0c;允许用户轻松创建动态测试脚本。本文将…

解决python bug(关于Paddle分布式训练):Exit with signal X

解决python bug[关于Paddle分布式训练]&#xff1a;Exit with signal X 多卡训练(需根据自己需求进行更改)单卡训练(需根据自己需求进行更改) 本人在通过Paddle实现多目标检测跟踪时遇到了一个小bug。 Exit with signal X 后来通过查询相关资料得知&#xff0c;引发这个bug的原…