Linux:IO多路转接之select

文章目录

  • select
    • timeval结构体
    • fd_set
  • 优缺点分析
  • 完整代码

本节要介绍的主题是多路转接式IO

select

先说结论,这个select是做什么的呢?

select是负责在Linux系统中,让一个人可以有多个鱼竿,可以不停的进行轮询,只要有一个准备好了就可以进行等待,先看一下它的函数参数:

在这里插入图片描述
这个函数参数还是有点复杂的,下面对于这些函数的参数进行解析:

首先是第一个参数nfds,这个参数的值是最大的文件描述符的值加一,比如现在有1234,对于这四个文件描述符来说,要填写的第一个参数的值就是5

下面看一下返回值:

在这里插入图片描述
简单来说,对于返回值n来说,如果n是大于0的,表示的是有n个fd已经就绪了,如果n是等于0的,表示的是超时,虽然没有错误,但是也没有资源就绪,如果n是小于0的,表示的是出错了,比如可能文件描述符被关了等等

timeval结构体

下面的参数是这个timeval结构体:

在这里插入图片描述
对于这个结构体来说,首先有两个成员,一个代表的是秒,一个代表的是微妙,这个参数的主要目的是给select设置一个等待的方式,比如可以进行一些合适的设置,使得这个select可以在规律的周期性醒来,如果要是把这个参数设置为0,表示的就是立马返回,其实就是一个非阻塞,不过一般也不这么设置,不过是可以这样设置的

同时需要注意的是,对于select当中,这个参数是一个输入输出参数,它不仅是输入,而且还会输出,输出的信息是剩余的时间,比如输入的是五秒钟,但是经过2秒钟资源就已经就绪了,那么就会返回3秒钟,表示还剩下3秒钟

fd_set

下面要进入的是select当中最重要的一个模块,fd_set类型的参数,这个参数是一个内核的数据类型,其实就是所谓的位图,这个参数主要是设置要监听什么事件,正常来说我们比较关心的是这个文件描述符的读写事件

比如现在要设置文件描述符是012的这三个内容,我们要关心它的写事件,那么就可以把位图的信息从0000 0000设置为0000 0111,而其中比特位的位置,表示的是文件描述符的编号,而其中的比特位的内容,表示的是这个东西内核是否需要关心

这个参数也是一个输入输出型的参数,在进行输入的时候,用户告诉内核,我要关心的是一个或者多个fd,你来帮我进行检测一下上面的读时间,如果要是检测到了,你要告诉我,而进行输出的时候,是内核告诉用户,你让我关心的这些事件当中,已经有xxx已经就绪了,你来进行读取吧,这就是这个位图可以带给用户的信息

说白了,这个位图的意义就是来让用户和内核进行交互,来查看fd是否已经就绪的信息的,这就意味着在进行select的操作当中,是有很多的位图操作的

那么下面,就用代码来对于这些内容进行验证:

// socket.hpp
#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"enum
{SocketErr = 2,BindErr,ListenErr,
};// TODO
const int backlog = 10;class Sock
{
public:Sock(){}~Sock(){}public:void Socket(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}int opt = 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));}void Bind(uint16_t port){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;if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(sockfd_, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);if(newfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const std::string &ip, const uint16_t &port){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));if(n == -1) {std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(sockfd_);}int Fd(){return sockfd_;}private:int sockfd_;
};
// selectserver.hpp
#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"
using namespace std;const uint16_t defaultport = 8888;class selectserver
{
public:selectserver(uint16_t port = defaultport) : _port(port){}~selectserver(){}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Start(){int listensock = _listensock.Fd();for (;;){fd_set rfds;FD_ZERO(&rfds);// 设置监听FD_SET(listensock, &rfds);struct timeval timeout = {1, 0};int n = select(5, &rfds, nullptr, nullptr, /*&timeout*/nullptr);switch (n){case 0:cout << "timeout : " << timeout.tv_sec << "." << timeout.tv_usec << endl;break;case -1:cerr << "select error" << endl;break;default:cout << "get a new link" << endl;// 对select进行处理break;}}}private:Sock _listensock;uint16_t _port;
};

对上述代码进行运行,进行链接后会发现,确实可以监听到效果

在这里插入图片描述
但是会非常快的打满整个屏幕,这告诉我们下面的结论

  1. 如果事件就绪了,但是上层不处理,select会一直通知用户
  2. select告诉就绪了,那么在接下来的一次读取的时候不会阻塞,因为事件已经就绪了

现在的这份代码注定是不完全的,起码对于建立的链接没有进行处理,所以下一步对于这样的链接要进行后续的处理,那现在的问题是,在进行处理的时候该如何进行处理?

由上面的结论可以看出,的确在select就绪的时候,说明下一次的读取是不会进行阻塞,可以直接进行读取的,因此在建立链接这件事上,是可以直接accept的,但是accept之后的内容呢?比如accept之后要进行接受数据,可以直接read吗?答案是不可以的,因为在建立链接之后用户未必会给你发消息,所以此时作为服务端要做的是要继续进行下一轮等待,再次进行read等待

所以等待也是要进行区分的,等的是accept还是read?所以在进行处理等到了的函数中,必然要对于等待的内容进行区分,如果等待的是accept,那么就建立链接,然后去等read,如果等待的是read,那么就可以直接去调用read了,所以下面继续对于这部分内容进行完善,我们要添加一个数组用来描述的建立的一个一个的文件描述符,位图的大小*8即可

void Dispatcher(fd_set &rfds)
{// 对于等待的信息进行循环等待for(int i = 0; i < fd_num_max; i++){int fd = fd_array[i];// 如果这个fd没被使用过,就跳过它if(fd == defaultfd)continue;if(FD_ISSET(fd, &rfds)){// 如果是建立链接的selectif(fd == _listensock.Fd())Accepter();// 如果是等待读取信息的selectelseRecver(fd, i);}}
}

如上所示的是一个基本的逻辑,对于要建立链接的select,就让他去建立链接,如果是要建立读取的select,就让他去执行读取的逻辑

那我们先处理建立链接的select:

void Accepter()
{// 接收客户端的ip和端口号string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport);if(sock < 0)return;lg(Info, "accept new link, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// 对于建立好的链接要去让它们进行等待selectint pos = 1;// 建立链接要进行判断select还有没有空余位置,如果select都满了,那对于建立新的链接就无能为力了for(; pos < fd_num_max; pos++){if(fd_array[pos] != defaultfd)continue;elsebreak;}// 如果当前select已经满了,说明已经不能再建立新的链接了if(pos == fd_num_max){lg(Warning, "server is full, close %d", sock);close(sock);}// 如果当前select没有满,那么就说明此时可以去进行等待了else{fd_array[pos] = sock;}
}

那如果当前识别到时要进行读取的select,说明接下来就可以直接进行读取了,不会进行阻塞了,下层已经把数据送上来了:

void Recver(int fd, int pos)
{char buffer[1024];// 此时可以直接进行读取,不会阻塞,因为已经是就绪了才会加到select当中的ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;cout << "get message " << buffer << endl;}else if (n == 0){// 如果是0,就说明客户端已经退出了,那么服务端也就不用维护这段链接了lg(Info, "client quit, server quit, close fd is %d", fd);close(fd);// 将对应的信息重新设置为-1,表示的是这个位置可以接收新的select了fd_array[pos] = defaultfd;}else{// 如果是这样,就是接收失败了,这里也把这个链接直接关掉就可以了lg(Warning, "read error, close fd is %d", fd);close(fd);// 将对应的信息重新设置为-1,表示的是这个位置可以接收新的select了fd_array[pos] = defaultfd;}
}

测试一下上面的代码

在这里插入图片描述
这样我们就完成了一个基本的select的多路转接

优缺点分析

优点

select有什么优点和缺点呢?对于select服务器来说,它的优点是比较明显的,因为它已经实现了一种多路转接的方案,在用单进程的方式实现了处理多个用户的请求,只要有内容就绪,那么就可以设置为就绪,用了一个辅助数组来标记到底有哪些数据已经就绪了

缺点

select的缺点也比较明显

  1. 等待的fd是有上限的,在我们当前这个版本来说,它能等待的最大值是1024,也就是说超过来了这个1024我们的处理方式是直接把链接的这个socket丢弃
  2. 输入输出型参数比较多,数据拷贝的频率比较高
  3. 输入输出型参数比较多,每次都要对关心的fd进行事件重置
  4. 在用户层来说,在使用第三方数组进行管理fd的时候,要进行很多次的遍历,在内核中检测fd的事件就绪的时候,也要进行遍历

完整代码

#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"
using namespace std;const uint16_t defaultport = 8888;
const int fd_num_max = sizeof(fd_set) * 8;
int defaultfd = -1;class selectserver
{
public:selectserver(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){fd_array[i] = defaultfd;}}~selectserver(){}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){// 接收客户端的ip和端口号string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport);if (sock < 0)return;lg(Info, "accept new link, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// 对于建立好的链接要去让它们进行等待selectint pos = 1;// 建立链接要进行判断select还有没有空余位置,如果select都满了,那对于建立新的链接就无能为力了for (; pos < fd_num_max; pos++){if (fd_array[pos] != defaultfd)continue;elsebreak;}// 如果当前select已经满了,说明已经不能再建立新的链接了if (pos == fd_num_max){lg(Warning, "server is full, close %d", sock);close(sock);}// 如果当前select没有满,那么就说明此时可以去进行等待了else{fd_array[pos] = sock;}}void Recver(int fd, int pos){char buffer[1024];// 此时可以直接进行读取,不会阻塞,因为已经是就绪了才会加到select当中的ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;cout << "get message " << buffer << endl;}else if (n == 0){// 如果是0,就说明客户端已经退出了,那么服务端也就不用维护这段链接了lg(Info, "client quit, server quit, close fd is %d", fd);close(fd);// 将对应的信息重新设置为-1,表示的是这个位置可以接收新的select了fd_array[pos] = defaultfd;}else{// 如果是这样,就是接收失败了,这里也把这个链接直接关掉就可以了lg(Warning, "read error, close fd is %d", fd);close(fd);// 将对应的信息重新设置为-1,表示的是这个位置可以接收新的select了fd_array[pos] = defaultfd;}}void Dispatcher(fd_set &rfds){// 对于等待的信息进行循环等待for (int i = 0; i < fd_num_max; i++){int fd = fd_array[i];// 如果这个fd没被使用过,就跳过它if (fd == defaultfd)continue;if (FD_ISSET(fd, &rfds)){// 如果是建立链接的selectif (fd == _listensock.Fd())Accepter();// 如果是等待读取信息的selectelseRecver(fd, i);}}}void Start(){int listensock = _listensock.Fd();fd_array[0] = listensock;for (;;){fd_set rfds;FD_ZERO(&rfds);// 设置监听int maxfd = fd_array[0];// 循环判断有哪些需要被监听for (int i = 0; i < fd_num_max; i++){if (fd_array[i] == defaultfd)continue;FD_SET(fd_array[i], &rfds);if (maxfd < fd_array[i]){maxfd = fd_array[i];lg(Info, "max fd update, max fd is: %d", maxfd);}}struct timeval timeout = {0, 0};int n = select(5, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);switch (n){case 0:cout << "timeout : " << timeout.tv_sec << "." << timeout.tv_usec << endl;break;case -1:cerr << "select error" << endl;break;default:cout << "get a new link" << endl;// 对select进行处理Dispatcher(rfds);break;}}}private:Sock _listensock;uint16_t _port;int fd_array[fd_num_max];
};

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

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

相关文章

Trace链异常检测汇总

微服务应用与单块应用完全不同&#xff0c;一个微服务系统少则有几十个微服务组成&#xff0c;多则可能有上百个服务。比如BAT级别的互联网公司&#xff0c;一般都超过上百个服务&#xff0c;服务之间的依赖关系错综复杂&#xff0c;如果没有有效的监控手段&#xff0c;那么出现…

[计算机知识] 各种小问题思考

哈希算法以及哈希冲突 哈希算法&#xff1a;将任何长度的输入通过散列函数转换成固定长度的字符串 哈希冲突&#xff1a;不同的输入经过哈希函数处理后得到相同的哈希值 因为哈希函数的输出域是有限的 解决哈希冲突&#xff1a; 1. 开放寻址&#xff1a;产生哈希冲突后&…

Vue - 1( 13000 字 Vue 入门级教程)

一&#xff1a;Vue 导语 1.1 什么是 Vue Vue.js&#xff08;通常称为Vue&#xff09;是一款流行的开源JavaScript框架&#xff0c;用于构建用户界面。Vue由尤雨溪在2014年开发&#xff0c;是一个轻量级、灵活的框架&#xff0c;被广泛应用于构建单页面应用&#xff08;SPA&am…

创建和启动线程

概述 Java语言的JVM允许程序运行多个线程&#xff0c;使用java.lang.Thread类代表线程&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。 Thread类的特性 每个线程都是通过某个特定Thread对象的run()方法来完成操作的&#xff0c;因此把run()方法体称为线程执行体。…

使用神经网络-遗传算法优化神经网络-风电预测故障(BP,GABP,matlab)

本项目是故障预测&#xff0c;不是时序预测&#xff0c;本质还是分类问题 1 数据集介绍 特征文件&#xff1a; 标签文件&#xff1a;共计4个标签&#xff0c;其中大多数都是正常的&#xff0c;其他是3个不正常的类别 2 使用BP网络 2.1 读取数据&#xff0c;然后选择几个…

【深度学习】图像自然语言描述生成

案例 6&#xff1a;图像自然语言描述生成&#xff08;让计算机“看图说话”&#xff09; 相关知识点&#xff1a;RNN、Attention 机制、图像和文本数据的处理 1 任务目标 1.1 任务和数据简介 ​ 本次案例将使用深度学习技术来完成图像自然语言描述生成任务&#xff0c;输入…

MySQL复制拓扑2

文章目录 主要内容一.配置基本复制结构1.分别在三台主机上停止mysqld服务&#xff0c;并对状态进行确认&#xff1a;代码如下&#xff08;示例&#xff09;: 2.对三个MySQL服务器的配置文件分别进行编辑&#xff0c;在[mysqld] 选项组中添加以下红色条目&#xff1a;3.在数据目…

深入理解Java异常处理机制(day20)

异常处理 异常处理是程序运行过程产生的异常情况进行恰当的处理技术 在计算机编程里面&#xff0c;异常的情况比所我们所想的异常情况还要多。 Java里面有两种异常处理方式&#xff1b; 1.利用trycatchfinaly语句处理异常&#xff0c;优点是分开了处理异常代码和程序正常代码…

深入浅出 -- 系统架构之负载均衡Nginx反向代理

一、Nginx反向代理-负载均衡 首先通过SpringBootFreemarker快速搭建一个WEB项目&#xff1a;springboot-web-nginx&#xff0c;然后在该项目中&#xff0c;创建一个IndexNginxController.java文件&#xff0c;逻辑如下&#xff1a; Controller public class IndexNginxControl…

【放假第3天】幻兽帕鲁 雾锁王国 我的世界 游戏云服务器选购指南 附最新价格对比表 新手、小白秒懂

更新日期&#xff1a;4月6日&#xff08;半年档 价格回调&#xff0c;京东云采购季持续进行&#xff09; 本文纯原创&#xff0c;侵权必究 【云服务器推荐】价格对比&#xff01;阿里云 京东云 腾讯云 选购指南视频截图 《最新对比表》已更新在文章头部—腾讯云文档&#xf…

前端三剑客 —— CSS (第四节)

目录 内容回顾&#xff1a; 1.常见样式 2.特殊样式 特殊样式 过滤效果 动画效果 动画案例&#xff1a; 渐变效果 其他效果&#xff1a; 多列效果 字体图标&#xff08;icon&#xff09; 内容回顾&#xff1a; 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的…

【话题:工作生活】2022年工作总结--疫情下的上海,疫情中的我。

现在是阳历2023年11月27日星期一&#xff0c;我再次开始撰写自己的年终工作总结。希望再过1、2个月&#xff0c;这份年终总结能够出炉&#xff0c;与大家相遇。 给自己定个小目标&#xff0c;年终的工作生活总结坚持写10年。我2017年毕业&#xff0c;之后就开始写每年的年终总结…

软考117-上午题-【计算机网络】-杂题+小结

一、杂题 真题1&#xff1a; 真题2&#xff1a; 真题3&#xff1a; 真题4&#xff1a; 真题5&#xff1a; 真题6&#xff1a; 真题7&#xff1a; 真题8&#xff1a; 真题9&#xff1a; 真题10&#xff1a; 真题11&#xff1a; 真题12&#xff1a; 真题13&#xff1a; 真题14&a…

c# wpf LiveCharts MVVM绑定 简单试验

1.概要 c# wpf LiveCharts MVVM绑定 简单试验 2.代码 <Window x:Class"WpfApp3.Window3"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://s…

Unity性能优化篇(十四) 其他优化细节以及UPR优化分析器

代码优化&#xff1a; 1. 使用AssetBundle作为资源加载方案。 而且经常一起使用的资源可以打在同一个AssetBundle包中。尽量避免同一个资源被打包进多个AB包中。压缩方式尽量使用LZ4&#xff0c;少用或不要用LZMA的压缩方式。如果确定后续开发不会升级Unity版本&#xff0c;则可…

MySql并发事务问题

事务 事务概念&#xff1a; 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性&#xff1a;ACID&#xff1a; 小…

鸿蒙实战开发-如何使用Stage模型卡片

介绍 本示例展示了Stage模型卡片提供方的创建与使用。 用到了卡片扩展模块接口&#xff0c;ohos.app.form.FormExtensionAbility 。 卡片信息和状态等相关类型和枚举接口&#xff0c;ohos.app.form.formInfo 。 卡片提供方相关接口的能力接口&#xff0c;ohos.app.form.for…

开源流程图表库(04):mxGraph,都是可视化编辑,导出使用。

mxGraph是一个用于创建和展示图形的JavaScript库。它提供了丰富的功能和工具&#xff0c;可以用于构建各种类型的图形应用程序&#xff0c;包括流程图、组织结构图、网络拓扑图等。 mxGraph的编辑器 一、mxGraph的特点和功能 以下是一些mxGraph的特点和功能&#xff1a; 强大…

耐腐蚀耐高温实验室塑料烧杯进口高纯PFA材质反应器特氟龙烧杯

PFA烧杯在实验过程中可作为储酸容器或涉及强酸强碱类实验的反应容器&#xff0c;用于盛放样品、试剂&#xff0c;可搭配电热板加热、蒸煮、赶酸用。 外壁均有凸起刻度&#xff0c;直筒设计&#xff0c;带翻边&#xff0c;便于夹持和移动&#xff0c;边沿有嘴&#xff0c;便于倾…

数学矩阵GCD和lCM(详解)

矩阵乘法 知阵乘法是《线性代数》中的基础内容&#xff0c;但在考察数学的算法题中也会出现。 本节我们学习基础的矩阵乘法规则。 每个矩阵会有一个行数和一个列数&#xff0c;只有当相乘的两个矩阵的左矩阵的列数等于右矩阵的行数 时&#xff0c;才能相乘&#xff0c;否则不允…