Linux——多路复用之select

目录

前言

一、select的认识

二、select的接口

三、select的使用

四、select的优缺点


前言

在前面,我们学习了五种IO模型,对IO有了基本的认识,知道了select效率很高,可以等待多个文件描述符,那他是如何等待的呢?我们又该如何使用呢?

一、select的认识

系统提供select函数来实现多路复用输入/输出模型

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

select只负责等待,不负责拷贝,一次可以等待多个文件描述符。他的作用是让read和write不再阻塞

二、select的接口

select的调用接口如下

参数 1 int nfds:值最大的文件描述符+1。

参数 2 fd_set* readfds:fd_set本质是一张位图。代表select需要关心的读事件

参数 3 fd_set* writefds:代表select需要关心的读事件

参数 4 fd_set* execptfdsfds:代表select需要关心的异常事件,我们暂时不考虑

参数 5 struct timeval* timeout:时间结构体,成员有秒和微秒,代表等待的时间

                                                  {n,m}为阻塞等待n秒m微秒,时间结束后返回

                                                  {0,0}为非阻塞等待

                                                  nullptr为阻塞等待

参数2,3,4类似,都是输入输出型参数,参数5也是输入输出型参数,输出的是剩余时间

以readfds为例

输入时:比特位的位置,表示文件描述符的值,比特位的内容(0/1),用户关心内核,是否关心这个fd的读事件。

输出时:比特位的位置,表示文件描述符的值,比特位的内容(0/1),内核告诉用户,哪些文件fd上的读事件是否就绪

返回值:

  1. ret  >  0 :select等待的多个fd中,已经就需要的fd个数
  2. ret == 0 :select超时返回
  3. ret  <  0 :select出错

同时,fd_set 是特定的类型,我们对其赋值时,是不方便赋值的,因此库里面也给提供的一个函数,方便我们处理。

FD_CLR                    从文件描述符集合 set 中清除文件描述符 fd。

FD_ISSET                 检查文件描述符 fd 是否在文件描述符集合 set 中。

FD_SET                    将文件描述符 fd 添加到文件描述符集合 set 中。

FD_ZERO                 清空文件描述符集合 set,将其所有位都设置为零。

三、select的使用

Log.hpp

#pragma once#include <iostream>
#include <cstdarg>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
using namespace std;enum
{Debug = 0,Info,Warning,Error,Fatal
};enum
{Screen = 10,OneFile,ClassFile
};string LevelToString(int level)
{switch (level){case Debug:return "Debug";case Info:return "Info";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "Unknown";}
}const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";class Log
{
public:Log(int style = default_style, string filename = default_filename): _style(style), _filename(filename){if (_style != Screen)mkdir(logdir.c_str(), 0775);}// 更改打印方式void Enable(int style){_style = style;if (_style != Screen)mkdir(logdir.c_str(), 0775);}// 时间戳转化为年月日时分秒string GetTime(){time_t currtime = time(nullptr);struct tm *curr = localtime(&currtime);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday, curr->tm_hour, curr->tm_min, curr->tm_sec);return time_buffer;}// 写入到文件中void WriteLogToOneFile(const string &logname, const string &message){FILE *fp = fopen(logname.c_str(), "a");if (fp == nullptr){perror("fopen failed");exit(-1);}fprintf(fp, "%s\n", message.c_str());fclose(fp);}// 打印日志void WriteLogToClassFile(const string &levelstr, const string &message){string logname = logdir;logname += "/";logname += _filename;logname += levelstr;WriteLogToOneFile(logname, message);}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void WriteLog(const string &levelstr, const string &message){pthread_mutex_lock(&lock);switch (_style){case Screen:cout << message << endl; // 打印到屏幕中break;case OneFile:WriteLogToClassFile("all", message); // 给定all,直接写到all里break;case ClassFile:WriteLogToClassFile(levelstr, message); // 写入levelstr里break;default:break;}pthread_mutex_unlock(&lock);}// 提供接口给运算符重载使用void _LogMessage(int level, const char *file, int line, char *rightbuffer){char leftbuffer[1024];string levelstr = LevelToString(level);string currtime = GetTime();string  idstr = to_string(getpid());snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s][%s:%d]", levelstr.c_str(), currtime.c_str(), idstr.c_str(), file, line);string messages = leftbuffer;messages += rightbuffer;WriteLog(levelstr, messages);}// 运算符重载void operator()(int level, const char *file, int line, const char *format, ...){char rightbuffer[1024];va_list args;                                              // va_list 是指针va_start(args, format);                                    // 初始化va_list对象,format是最后一个确定的参数vsnprintf(rightbuffer, sizeof(rightbuffer), format, args); // 写入到rightbuffer中va_end(args);_LogMessage(level, file, line, rightbuffer);}~Log(){}private:int _style;string _filename;
};Log lg;class Conf
{
public:Conf(){lg.Enable(Screen);}~Conf(){}
};Conf conf;// 辅助宏
#define lg(level, format, ...) lg(level, __FILE__, __LINE__, format, ##__VA_ARGS__)

Socket.hpp 

#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
using namespace std;
namespace Net_Work
{static const int default_backlog = 5;static const int default_sockfd = -1;using namespace std;enum{SocketError = 1,BindError,ListenError,ConnectError,};// 封装套接字接口基类class Socket{public:// 封装了socket相关方法virtual ~Socket() {}virtual void CreateSocket() = 0;virtual void BindSocket(uint16_t port) = 0;virtual void ListenSocket(int backlog) = 0;virtual bool ConnectSocket(string &serverip, uint16_t serverport) = 0;virtual Socket *AcceptSocket(string *peerip, uint16_t *peerport) = 0;virtual int GetSockFd() = 0;virtual void SetSockFd(int sockfd) = 0;virtual void CloseSocket() = 0;virtual bool Recv(string *buff, int size) = 0;virtual void Send(string &send_string) = 0;// 方法的集中在一起使用public:void BuildListenSocket(uint16_t port, int backlog = default_backlog){CreateSocket();BindSocket(port);ListenSocket(backlog);}bool BuildConnectSocket(string &serverip, uint16_t serverport){CreateSocket();return ConnectSocket(serverip, serverport);}void BuildNormalSocket(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = default_sockfd): _sockfd(sockfd){}~TcpSocket() {}void CreateSocket() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0)exit(SocketError);}void BindSocket(uint16_t port) override{int opt = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0)exit(BindError);}void ListenSocket(int backlog) override{int n = listen(_sockfd, backlog);if (n < 0)exit(ListenError);}bool ConnectSocket(string &serverip, uint16_t serverport) override{struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(serverport);// addr.sin_addr.s_addr = inet_addr(serverip.c_str());inet_pton(AF_INET, serverip.c_str(), &addr.sin_addr);int n = connect(_sockfd, (sockaddr *)&addr, sizeof(addr));if (n == 0)return true;return false;}Socket *AcceptSocket(string *peerip, uint16_t *peerport) override{struct sockaddr_in addr;socklen_t len = sizeof(addr);int newsockfd = accept(_sockfd, (sockaddr *)&addr, &len);if (newsockfd < 0)return nullptr;// *peerip = inet_ntoa(addr.sin_addr);// INET_ADDRSTRLEN 是一个定义在头文件中的宏,表示 IPv4 地址的最大长度char ip_str[INET_ADDRSTRLEN];inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);*peerip = ip_str;*peerport = ntohs(addr.sin_port);Socket *s = new TcpSocket(newsockfd);return s;}int GetSockFd() override{return _sockfd;}void SetSockFd(int sockfd) override{_sockfd = sockfd;}void CloseSocket() override{if (_sockfd > default_sockfd)close(_sockfd);}bool Recv(string *buff, int size) override{char inbuffer[size];ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);if (n > 0){inbuffer[n] = 0;*buff += inbuffer;return true;}elsereturn false;}void Send(string &send_string) override{send(_sockfd, send_string.c_str(),send_string.size(),0);}private:int _sockfd;string _ip;uint16_t _port;};
}

        select只负责等待,不负责处理,最初我们有一个listen_sock需要交给select去管理,当有新链接到来是,listen_sock要去接受新链接,但是接受后,不能立刻read或者write,因为不确定当前事件是否就绪,需要将新链接也交给select管理

        如何将新链接交给select呢?我们得有一个数据结构(这里用的数组),把所有的fd都管理起来,新链接到来时,都可以往这个数组里面添加文件描述符fd。后面select遍历数组,就可以找到需要管理的fd了,但这样,我们需要经常遍历这个数组

  1. 添加时需要遍历找到空再插入
  2. select传参,需要遍历查找最大的文件描述符
  3. select等待成功后调用处理函数时,也需遍历查找就绪的文件描述符

        同时,由于select的事件参数是一个输入输出型参数,因此我们每次都得重新对该参数重新赋值。

如下是SelectServer.hpp的核心代码 

SelectServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/select.h>
#include "Log.hpp"
#include "Socket.hpp"using namespace Net_Work;
const static int gdefaultport = 8888;
const static int gbacklog = 8;
const static int num = sizeof(fd_set) * 8;class SelectServer
{
public:SelectServer(int port) : _port(port), _listensock(new TcpSocket()){}void HandlerEvent(fd_set rfds){for (int i = 0; i < num; i++){if (_rfds_array[i] == nullptr)continue;int fd = _rfds_array[i]->GetSockFd();// 判断事件是否就绪if (FD_ISSET(fd, &rfds)){// 读事件分两类,一类是新链接到来,一类是新数据到来if (fd == _listensock->GetSockFd()){// 新链接到来lg(Info, "get a new link");// 获取连接std::string clientip;uint16_t clientport;Socket *sock = _listensock->AcceptSocket(&clientip, &clientport);if (!sock){lg(Error, "accept error");return;}lg(Info, "get a client,client info is# %s:%d,fd: %d", clientip.c_str(), clientport, sock->GetSockFd());// 此时获取连接成功了,但是不能直接read write,sockfd仍需要交给select托管 -- 添加到数组_rfds_array中int pos = 0;for (; pos < num; pos++){if (_rfds_array[pos] == nullptr){_rfds_array[pos] = sock;lg(Info, "get a new link, fd is : %d", sock->GetSockFd());break;}}if (pos == num){sock->CloseSocket();delete sock;lg(Warning, "server is full, be carefull...");}}else{// 普通的读事件就绪std::string buffer;bool res = _rfds_array[i]->Recv(&buffer, 1024);if (res){lg(Info,"client say# %s",buffer.c_str());buffer+=": 你好呀,同志\n";_rfds_array[i]->Send(buffer);buffer.clear();}else{lg(Warning,"client quit ,maybe close or error,close fd: %d",fd);_rfds_array[i]->CloseSocket();delete _rfds_array[i];_rfds_array[i] = nullptr;}}}}}void InitServer(){_listensock->BuildListenSocket(_port, gbacklog);for (int i = 0; i < num; i++){_rfds_array[i] = nullptr;}_rfds_array[0] = _listensock.get();}void Loop(){_isrunning = true;// 循环重置select需要的rfdswhile (_isrunning){// 不能直接获取新链接,因为accpet可能阻塞// 所有的fd,都要交给select,listensock上面新链接,相当于读事件// 因此需要将listensock交给select// 遍历数组, 1.找最大的fd  2. 合法的fd添加到rfds集合中fd_set rfds;FD_ZERO(&rfds);int max_fd = _listensock->GetSockFd();for (int i = 0; i < num; i++){if (_rfds_array[i] == nullptr){continue;}else{// 添加fd到集合中int fd = _rfds_array[i]->GetSockFd();FD_SET(fd, &rfds);if (max_fd < fd) // 更新最大值{max_fd = fd;}}}// 定义时间struct timeval timeout = {0, 0};PrintDebug();// rfds是输入输出型参数,rfds是在select调用返回时,不断被修改,所以每次需要重置rfdsint n = select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);switch (n){case 0:lg(Info, "select timeout...,last time: %u.%u", timeout.tv_sec, timeout.tv_usec);break;case -1:lg(Error, "select error!!!");default:// 正常就绪的fdlg(Info, "select success,begin event handler,last time: %u.%u", timeout.tv_sec, timeout.tv_usec);HandlerEvent(rfds); break;}}_isrunning = false;}void Stop(){_isrunning = false;}void PrintDebug(){std::cout << "current select rfds list is :";for (int i = 0; i < num; i++){if (_rfds_array[i] == nullptr)continue;elsestd::cout << _rfds_array[i]->GetSockFd() << " ";}std::cout << std::endl;}private:std::unique_ptr<Socket> _listensock;int _port;bool _isrunning;// select 服务器要被正确设计,需要程序员定义数据结构,来吧所有的fd管理起来Socket *_rfds_array[num];
};

 Main.cc

#include <iostream>
#include <memory>
#include "SelectServer.hpp"void Usage(char* argv)
{std::cout<<"Usage: \n\t"<<argv<<" port\n"<<std::endl;
}
// ./select_server 8080
int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);return -1;}uint16_t localport = std::stoi(argv[1]);std::unique_ptr<SelectServer> svr = std::make_unique<SelectServer>(localport);svr->InitServer();svr->Loop();return 0;
}

四、select的优缺点

优点:select只负责等待,可以等待多个fd,IO的时候,效率会比较高一些。

缺点:

  1. 由于select是输入输出型参数,因此我们每次都要对select的参数重新设置。
  2. 编写代码时,select因为要使用第三方数组,充满了遍历,这可能会影响select的效率。
  3. 用户到内核,内核到用户,每次select调用和返回,都要对位图重新设置,用户和内核之间,要一直进行数据拷贝。
  4. select让OS在底层遍历需要关心所有的fd,这也会造成效率低下,这也是为何第一个参数需要传入max_fd + 1,就是因为select的底层需要遍历。
  5. fd_set 是系统提供的类型,fd_set大小是固定的,就意味着位图的个数是固定的,也就是select最多能够检测到fd的总数是有上限的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频活码如何在线制作?分享快速制作二维码的方法

视频想要快速的分享现在有很多的人会选择二维码的方式&#xff0c;将视频转换成二维码通过手机扫码就能够快速在线查看视频内容&#xff0c;这样可以不占用扫码者自身的内存&#xff0c;随时扫码从云端调取内容查看&#xff0c;更加的方便快捷便于内容的分享。那么具体该如何实…

搭建个人智能家居 7 - 空气颗粒物检测

搭建个人智能家居 7 - 空气颗粒物检测 前言说明PMS5003ESPHomeHomeAssistant结束 前言 到目前为止&#xff0c;我们这个智能家居系统添加了4个外设&#xff0c;分别是&#xff1a;LED灯、RGB灯、DHT11温度传感器和SGP30。今天继续添加环境测量类传感器“PMS5003空气颗粒物检测…

【Leetcode】二十一、前缀树 + 词典中最长的单词

文章目录 1、背景2、前缀树Trie3、leetcode208&#xff1a;实现Trie4、leetcode720&#xff1a;词典中最长的单词 1、背景 如上&#xff0c;以浏览器搜索时的自动匹配为例&#xff1a; 如果把所有搜索关键字放一个数组里&#xff0c;则&#xff1a;插入、搜索一个词条时&#x…

VisualRules-Web案例展示(一)

VisualRules单机版以其卓越的功能深受用户喜爱。现在&#xff0c;我们进一步推出了VisualRules-Web在线版本&#xff0c;让您无需安装任何软件&#xff0c;即可在任何浏览器中轻松体验VisualRules的强大功能。无论是数据分析、规则管理还是自动化决策&#xff0c;VisualRules-W…

【深度学习】PyTorch框架(3):优化与初始化

1.引言 在本文中&#xff0c;我们将探讨神经网络的优化与初始化技术。随着神经网络深度的增加&#xff0c;我们会遇到多种挑战。最关键的是确保网络中梯度流动的稳定性&#xff0c;否则可能会遭遇梯度消失或梯度爆炸的问题。因此&#xff0c;我们将深入探讨以下两个核心概念&a…

VScode:前端项目中yarn包的安装和使用

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端&#xff0c;安装yarn插件 输入 npm ins…

活动回顾 | AutoMQ 联合 GreptimeDB 共同探讨新能源汽车数据基础设施

7 月 13 日&#xff0c;AutoMQ 携手 GreptimeDB“新能源汽车数据基础设施” 主题 meetup 在上海圆满落幕。本次论坛多角度探讨如何通过创新的数据管理和存储架构&#xff0c;提升汽车系统的性能、安全性和可靠性&#xff0c;从而驱动行业的持续发展和创新&#xff0c;涵盖 Auto…

全时守护,无死角监测:重点海域渔港视频AI智能监管方案

一、方案背景 随着海洋经济的快速发展和海洋资源的日益紧缺&#xff0c;对重点海域渔港进行有效监控和管理显得尤为重要。视频监控作为一种高效、实时的管理手段&#xff0c;已成为渔港管理中不可或缺的一部分。当前&#xff0c;我国海域面积广阔&#xff0c;渔港众多&#xf…

QT CNA上位机报错 解决方案

QT编译报错: -lControlCAN 解决方案 更换三个文件&#xff0c;即可解决(QT 自带的是32位库&#xff0c;应使用64位库文件)

docker desktop历史版本安装

1.安装choco Windows安装 choco包管理工具-CSDN博客 2.通过choco安装 下面例子为安装旧版2.3.0.2,其它版本类似 Chocolatey Software | Docker Desktop 2.3.0.2 https://download.docker.com/win/stable/45183/Docker%20Desktop%20Installer.exe choco install docker-des…

【postgresql】pg_dump备份数据库

pg_dump 介绍 pg_dump 是一个用于备份 PostgreSQL 数据库的实用工具。它可以将数据库的内容导出为一个 SQL 脚本文件或其他格式的文件&#xff0c;以便在需要时进行恢复或迁移。 基本用法 pg_dump [选项] [数据库名] 命令选项 -h 或 --host&#xff1a;指定数据库服务器的主…

跟着操作,解决iPhone怎么清理内存难题

在如今智能手机功能日益强大的时代&#xff0c;我们使用手机拍照、录制视频、下载应用、存储文件等操作都会占用手机内存。当内存空间不足时&#xff0c;手机运行会变得缓慢&#xff0c;甚至出现卡顿、闪退等现象。因此&#xff0c;定期清理iPhone内存是非常必要的。那么&#…

力扣第十七题——电话号码的字母组合

内容介绍 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出…

iredmail服务器安装步骤详解!如何做配置?

iredmail服务器安全性设置指南&#xff1f;怎么升级邮件服务器&#xff1f; iredmail是一个功能强大的邮件服务器解决方案&#xff0c;它集成了多个开源软件&#xff0c;使您能够快速部署和管理邮件服务。AokSend将逐步引导您完成安装过程&#xff0c;无需深入的编程知识即可轻…

【ai】学习笔记:电影推荐1:协同过滤 TF-DF 余弦相似性

2020年之前都是用协同过滤2020年以后用深度学习、人工智能视频收费的,不完整,里面是电影推荐 这里有个视频讲解2016年大神分析了电影推荐 :MovieRecommendation github地址 看起来是基于用户的相似性和物品的相似性,向用户推荐物品: 大神的介绍: 大神的介绍: 基于Pytho…

海外营销推广:快速创建维基百科(wiki)词条-大舍传媒

一、维基百科的永久留存问题 许多企业和个人关心维基百科是否能永久留存。实际上&#xff0c;只要企业和个人的行为没有引起维基百科管理方的反感&#xff0c;词条就可以长期保存。如果有恶意行为或被投诉&#xff0c;维基百科可能会对词条进行删除或修改。 二、创建维基百科…

Python项目打包与依赖管理指南

在Python开发中&#xff0c;python文件需要在安装有python解释器的计算机的电脑上才能运行&#xff0c;但是在工作时&#xff0c;我们需要给客户介绍演示项目功能时并不一定可以条件安装解释器&#xff0c;而且这样做非常不方便。这时候我们可以打包项目&#xff0c;用于给客户…

平价养猫必看!测评几十款选出的最值得入手的希喂主食冻干

各位铲屎官&#xff0c;今天来聊聊我近期发现的宝藏主食冻干——希喂CPMR2.0大橙罐。平价养猫必看&#xff01;&#xff0c;在追求猫咪饮食健康与自身预算平衡的路上&#xff0c;我尝试了多种产品&#xff0c;而希喂以其高含肉量和高营养价值脱颖而出。它让喂食变得多样化且高效…

【STM32】LED闪烁LED流水灯蜂鸣器(江科大)

LED正极&#xff1a;外部长脚、内部较小 LED负极&#xff1a;外部短脚、内部较大 LED电路 限流电阻&#xff1a;保护LED&#xff0c;调节LED亮度&#xff08;本实验用面包板为了方便&#xff0c;省去了限流电阻&#xff0c;设计电路时要加上&#xff09; 左上图&#xff1a;低…

【golang-ent】go-zero框架 整合 ent orm框架 | 解决left join未关联报错的问题

一、场景 1、子表&#xff1a;cp_member_point_history cp_member_point_history表中字段&#xff1a;cp_point_reward_id 是cp_point_reward的主键id 当本表中的cp_point_reward_id字段为0&#xff08;即&#xff1a;没有可关联主表的&#xff09; CREATE TABLE cp_member_poi…