I/O多路转接之select

初识select

系统提供 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() 的等待时间
参数 timeout 取值 :
NULL :则表示 select ()没有 timeout select 将一直被阻塞,直到某个文件描述符上发生了事件 ;
0 :仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在指定的时间段里没有事件发生, select 将超时返回。
关于 fd_set 结构

 

其实这个结构就是一个整数数组, 更严格的说, 是一个 "位图". 使用位图中对应的位来表示要监视的文件描述符

提供了一组操作fd_set的接口, 来比较方便的操作位图

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的全部位
关于 timeval 结构
timeval 结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

函数返回值:

执行成功则返回文件描述词状态已改变的个数

如果返回0代表在描述词状态改变前已超过timeout时间,没有返回

当有错误发生时则返回-1,错误原因存于errno,此时参数readfdswritefds, exceptfdstimeout的 值变成不可预测

错误值可能为:

EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
常见的程序片段如下 :
 fs_set readset;
 FD_SET(fd,&readset);
 select(fd+1,&readset,NULL,NULL,NULL);
 if(FD_ISSET(fd,readset)){……}

理解select执行过程

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

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 就绪条件
读就绪
socket 内核中 , 接收缓冲区中的字节数 , 大于等于低水位标记 SO_RCVLOWAT. 此时可以无阻塞的读该文件 描述符, 并且返回值大于 0;
socket TCP 通信中 , 对端关闭连接 , 此时对该 socket , 则返回 0;
监听的socket 上有新的连接请求 ;
socket 上有未处理的错误 ;
写就绪
socket 内核中 , 发送缓冲区中的可用字节数 ( 发送缓冲区的空闲位置大小 ), 大于等于低水位标记
SO_SNDLOWAT, 此时可以无阻塞的写 , 并且返回值大于 0;
socket 的写操作被关闭 (close 或者 shutdown). 对一个写操作被关闭的 socket 进行写操作 , 会触发 SIGPIPE 信号;
socket 使用非阻塞 connect 连接成功或失败之后 ;
socket 上有未读取的错误 ;
select 的特点
可监控的文件描述符个数取决与 sizeof(fd_set) 的值 . 我这边服务器上 sizeof(fd_set) 512 ,每 bit 表示一个文件 描述符,则我服务器上支持的最大文件描述符是512*8=4096. 将fd 加入 select 监控集的同时,还要再使用一个数据结构 array 保存放到 select 监控集中的 fd
一是用于再 select 返回后, array 作为源数据和 fd_set 进行 FD_ISSET 判断。
二是 select 返回后会把以前加入的但并无事件发生的 fd 清空,则每次开始 select 前都要重新从 array 取得 fd逐一加入 (FD_ZERO 最先 ) ,扫描 array 的同时取得 fd 最大值 maxfd ,用于 select 的第一个参数。
备注 : fd_set 的大小可以调整,可能涉及到重新编译内核
select 缺点
每次调用 select, 都需要手动设置 fd 集合 , 从接口使用角度来说也非常不便 .
每次调用 select ,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大
同时每次调用 select 都需要在内核遍历传递进来的所有 fd ,这个开销在 fd 很多时也很大
select 支持的文件描述符数量太小
select 使用示例 : 检测标准输入输出
只检测标准输入
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main() {
 fd_set read_fds;
 FD_ZERO(&read_fds);
 FD_SET(0, &read_fds);
 for (;;) {
 printf("> ");
 fflush(stdout);
 int ret = select(1, &read_fds, NULL, NULL, NULL);
 if (ret < 0) {
 perror("select");
 continue;
 }
 if (FD_ISSET(0, &read_fds)) {
 char buf[1024] = {0};
 read(0, buf, sizeof(buf) - 1);
 printf("input: %s", buf);
 } else {
 printf("error! invaild fd\n");
 continue;
 }
 FD_ZERO(&read_fds);
 FD_SET(0, &read_fds);
 }
 return 0;
}
当只检测文件描述符 0 (标准输入)时,因为输入条件只有在你有输入信息的时候,才成立,所以如果 一直不输入,就会产生超时信息
select 使用示例
使用 select 实现字典服务器
tcp_socket.hpp
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) \
    if (!(exp))        \
    {                  \
        return false;  \
    }                  \

class TcpSocket    
{
public:
TcpSocket() : fd_(-1) {}
TcpSocket(int fd) : fd_(fd) {}
bool Socket()
{
    fd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (fd_ < 0)
    {
        perror("socket");
        return false;
    }
    printf("open fd = %d\n", fd_);
    return true;
}
bool Close() const
{
    close(fd_);
    printf("close fd = %d\n", fd_);
    return true;
}
bool Bind(const std::string &ip, uint16_t port) const
{
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    int ret = bind(fd_, (sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("bind");
        return false;
    }
    return true;
}
bool Listen(int num) const
{
    int ret = listen(fd_, num);
    if (ret < 0)
    {
        perror("listen");
        return false;
    }
    return true;
}
bool Accept(TcpSocket *peer, std::string *ip = NULL, uint16_t *port = NULL) const
{
    sockaddr_in peer_addr;
    socklen_t len = sizeof(peer_addr);
    int new_sock = accept(fd_, (sockaddr *)&peer_addr, &len);
    if (new_sock < 0)
    {
        perror("accept");
        return false;
    }
    printf("accept fd = %d\n", new_sock);
    peer->fd_ = new_sock;
    if (ip != NULL)
    {
        *ip = inet_ntoa(peer_addr.sin_addr);
    }
    if (port != NULL)
    {
        *port = ntohs(peer_addr.sin_port);
    }
    return true;
}
bool Recv(std::string *buf) const
{
    buf->clear();
    char tmp[1024 * 10] = {0};
    // [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
    // 参考 man 手册 MSG_WAITALL 节.
    ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
    if (read_size < 0)
    {
        perror("recv");
        return false;
    }
    if (read_size == 0)
    {
        return false;
    }
    buf->assign(tmp, read_size);
    return true;
}
bool Send(const std::string &buf) const
{
    ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
    if (write_size < 0)
    {
        perror("send");
        return false;
    }
    return true;
}
bool Connect(const std::string &ip, uint16_t port) const
{
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    addr.sin_port = htons(port);
    int ret = connect(fd_, (sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("connect");
        return false;
    }
    return true;
}
int GetFd() const
{
    return fd_;
}

private:
int fd_;
}
;

tcp_select_server.hpp

#pragma once
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/select.h>
#include "tcp_socket.hpp"
// 必要的调试函数
inline void PrintFdSet(fd_set *fds, int max_fd)
{
    printf("select fds: ");
    for (int i = 0; i < max_fd + 1; ++i)
    {
        if (!FD_ISSET(i, fds))
        {
            continue;
        }
        printf("%d ", i);
    }
    printf("\n");
}
typedef std::function<void(const std::string &req, std::string *resp)> Handler;
// 把 Select 封装成一个类. 这个类虽然保存很多 TcpSocket 对象指针, 但是不管理内存
class Selector
{
public:
    Selector()
    {
        // [注意!] 初始化千万别忘了!!
        max_fd_ = 0;
        FD_ZERO(&read_fds_);
    }
    bool Add(const TcpSocket &sock)
    {
        int fd = sock.GetFd();
        printf("[Selector::Add] %d\n", fd);
        if (fd_map_.find(fd) != fd_map_.end())
        {
            printf("Add failed! fd has in Selector!\n");
            return false;
        }
        fd_map_[fd] = sock;
        FD_SET(fd, &read_fds_);
        if (fd > max_fd_)
        {
            max_fd_ = fd;
        }
        return true;
    }
    bool Del(const TcpSocket &sock)
    {
        int fd = sock.GetFd();
        printf("[Selector::Del] %d\n", fd);
        if (fd_map_.find(fd) == fd_map_.end())
        {
            printf("Del failed! fd has not in Selector!\n");
            return false;
        }
        fd_map_.erase(fd);
        FD_CLR(fd, &read_fds_);
        // 重新找到最大的文件描述符, 从右往左找比较快
        for (int i = max_fd_; i >= 0; --i)
        {
            if (!FD_ISSET(i, &read_fds_))
            {
                continue;
            }
            max_fd_ = i;
            break;
        }
        return true;
    }
    // 返回读就绪的文件描述符集
    bool Wait(std::vector<TcpSocket> *output)
    {
        output->clear();
        // [注意] 此处必须要创建一个临时变量, 否则原来的结果会被覆盖掉
        fd_set tmp = read_fds_;
        // DEBUG
        PrintFdSet(&tmp, max_fd_);
        int nfds = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);
        if (nfds < 0)
        {
            perror("select");
            return false;
        }
        // [注意!] 此处的循环条件必须是 i < max_fd_ + 1
        for (int i = 0; i < max_fd_ + 1; ++i)
        {
            if (!FD_ISSET(i, &tmp))
            {
                continue;
            }
            output->push_back(fd_map_[i]);
        }
        return true;
    }

private:
    fd_set read_fds_;
    int max_fd_;
    // 文件描述符和 socket 对象的映射关系
    std::unordered_map<int, TcpSocket> fd_map_;
};
class TcpSelectServer
{
public:
    TcpSelectServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
    {
    }
    bool Start(Handler handler) const
    {
        // 1. 创建 socket
        TcpSocket listen_sock;
        bool ret = listen_sock.Socket();
        if (!ret)
        {
            return false;
        }
        // 2. 绑定端口号
        ret = listen_sock.Bind(ip_, port_);
        if (!ret)
        {
            return false;
        }
        // 3. 进行监听
        ret = listen_sock.Listen(5);
        if (!ret)
        {
            return false;
        }
        // 4. 创建 Selector 对象
        Selector selector;
        selector.Add(listen_sock);
        // 5. 进入事件循环
        for (;;)
        {
            std::vector<TcpSocket> output;
            bool ret = selector.Wait(&output);
            if (!ret)
            {
                continue;
            }
            // 6. 根据就绪的文件描述符的差别, 决定后续的处理逻辑
            for (size_t i = 0; i < output.size(); ++i)
            {
                if (output[i].GetFd() == listen_sock.GetFd())
                {
                    // 如果就绪的文件描述符是 listen_sock, 就执行 accept, 并加入到 select 中
                    TcpSocket new_sock;
                    listen_sock.Accept(&new_sock, NULL, NULL);
                    selector.Add(new_sock);
                }
                else
                {
                    // 如果就绪的文件描述符是 new_sock, 就进行一次请求的处理
                    std::string req, resp;
                    bool ret = output[i].Recv(&req);
                    if (!ret)
                    {
                        selector.Del(output[i]);
                        // [注意!] 需要关闭 socket
                        output[i].Close();
                        continue;
                    }
                    // 调用业务函数计算响应
                    handler(req, &resp);
                    // 将结果写回到客户端
                    output[i].Send(resp);
                }
            } // end for
        }     // end for (;;)
        return true;
    }

private:
    std::string ip_;
    uint16_t port_;
};

server.cc

#include <unordered_map>
#include "tcp_select_server.hpp"
std::unordered_map<std::string, std::string> g_dict;
void Translate(const std::string &req, std::string *resp)
{
    auto it = g_dict.find(req);
    if (it == g_dict.end())
    {
        *resp = "未找到";
        return;
    }
    *resp = it->second;
    return;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage ./dict_server [ip] [port]\n");
        return 1;
    }
    // 1. 初始化词典
    g_dict.insert(std::make_pair("hello", "你好"));
    g_dict.insert(std::make_pair("world", "世界"));
    g_dict.insert(std::make_pair("bit", "贼NB"));
    // 2. 启动服务器
    TcpSelectServer server(argv[1], atoi(argv[2]));
    server.Start(Translate);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud 使用sentinel

一、添加依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 二、配置文件配置地址 spring:cloud:sentinel:transport:dashboard: localhost:8080三…

【CSS】课程网站 网格商品展示 模块制作 ① ( 网格商品展示模块盒子模型测量及样式 | 顶部文本标题盒子测量及样式 | 代码示例 )

文章目录一、网格商品展示模块盒子模型测量及样式1、盒子尺寸测量2、标题盒子尺寸测量和样式3、左侧文本盒子尺寸测量和样式4、右侧文本盒子尺寸测量和样式二、顶部文本标题盒子代码示例1、HTML 标签结构2、CSS 样式3、展示效果绘制矩形框中的部分 : 一、网格商品展示模块盒子…

【服务器数据恢复】NTFS分区被格式化如何恢复数据?

服务器数据恢复环境&故障&#xff1a; 误操作格式化服务器RAID5磁盘阵列下的分区&#xff08;NTFS文件系统&#xff09;。 服务器数据恢复过程&#xff1a; 1、将故障服务器连接到北亚企安备份服务器上&#xff0c;将故障服务器的所有硬盘设置为脱机状态&#xff0c;然后以…

什么是中间件?

一、什么是中间件&#xff1f; 1、百度百科 中间件是介于应用系统和系统软件之间的一类软件&#xff0c;它使用系统软件所提供的基础服务&#xff08;功能&#xff09;&#xff0c;衔接网络上应用系统的各个部分或不同的应用&#xff0c;能够达到资源共享、功能共享的目的。目…

手写简易 Spring(二)

文章目录手写简易 Spring&#xff08;二&#xff09;1. 扩展 BeanFactory 接口2. 实现资源加载器&#xff0c;从 Spring.xml 解析和注册 Bean 对象1. 核心实现类 XmlBeanDefinitionReader3. 实现应用上下文&#xff0c;自动识别、资源加载、扩展机制1. 应用上下文2. 核心实现类…

java基础之抽象类与接口

文章目录1.抽象方法和抽象类2.抽象类的作用3.接口4.接口和抽象类的异同5.面向接口编程1.抽象方法和抽象类 抽象方法和抽象类必须使用abstract修饰符来定义&#xff0c;有抽象方法的类只能被定义成抽象类&#xff0c;抽象类里可以没有抽象方法。 抽象类必须使用abstract修饰符来…

【Redis学习】Redis入门概述

Redis是什么 Redis:REmote Dictionary Server(远程字典服务器) 官网介绍&#xff1a;The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker.&#xff08;被数百万开发人员用作数据库、缓存、流…

在云服务部署前后端以及上传数据库

1.上传数据库(sql文件) 首先建立一个目录&#xff0c;用于存放要部署的sql文件&#xff0c;然后在此目录中进入mysql 进入后建立一个数据库&#xff0c;create database 数据库名 完成后&#xff0c;通过select * from 表名可以查到数据说明导入成功。 2.部署Maven后端 将Ma…

【预处理和程序环境】

预处理和程序环境一、程序的翻译环境和执行环境二、详解编译链接三、#define1. #define定义标识符2. #define定义宏3. #define的替换规则4. #和##4.1 #的使用4.2 ##的使用四、宏和函数对比五、条件编译一、程序的翻译环境和执行环境 我们的代码写完后称为源代码&#xff0c;源…

如何正确配置美国网络服务器?

在使用美国网络服务器时&#xff0c;充分注意其配置对于确保服务器和网络的性能、稳定性和安全性至关重要。网络服务器配置是指设置和配置网络服务器的硬件和软件以使其启动和运行的过程。它涉及多个步骤&#xff0c;包括配置操作系统、网络协议、安全设置、用户访问、共享资源…

linux驱动开发 - 01_字符设备驱动开发

文章目录字符设备驱动开发1. 字符设备驱动简介2 字符设备驱动开发步骤2.1 驱动模块的加载和卸载2.2 字符设备注册与注销2.3 实现设备的具体操作函数2.4 添加 LICENSE 和作者信息3 Linux 设备号3.1 设备号的组成3.2 设备号的分配4 chrdevbase 字符设备驱动开发实验4.1 实验程序编…

QT 之基础(一) 详解UI文件设计与运行机制

一、项目文件组成 1.1 创建一个项目文件 建立好项目如下 &#xff08;1&#xff09;项目组织文件【untitled.pro】 存储项目设置文件 QT core gui //表示项目中添加core gui模块 greaterThan(QT_MAJOR_VERSION, 4): QT widgets //条件执行语句&#xf…

【消息队列】聊一下如何避免消息的重复消费

什么是重复消费 一条消息在传输过程中&#xff0c;为了保证消息的不丢失&#xff0c;可能会多少量的消息进行重试&#xff0c;这样就可能导致Broker接受到的消息出现重复&#xff0c;如果说下游系统没有针对业务上的处理&#xff0c;那么可能导致同一笔借款或者支付订单出现重…

【Java多线程基础】Java线程的状态及主要转换方法

借鉴文献&#xff1a; 深入浅出Java多线程 注&#xff1a;《深入浅出Java多线程》是一本书&#xff0c;小编看的是它的 pdf&#xff0c;上面那个网页和书中内容是一致的。小编在起初学习多线程的时候是通过看视频的形式&#xff0c;学的模模糊糊的&#xff0c;这本书感觉很适合…

商务车改装后,有哪些情况会导致异响?

一、开车时出现了异响&#xff0c;主要由这些情况导致。&#xff08;1&#xff09;像商务车&#xff0c;二排基本都升级成了航空座椅&#xff0c;可能是出厂就有问题&#xff0c;拼接处没做好&#xff0c;导致座椅来回磨蹭。另外固定航空座椅螺丝没拧好&#xff0c;车子开动后会…

HTML5 <bdi> 标签、HTML5 <bdo> 标签

HTML5 <bdi> 标签 实例 将用户名从周围的文本方向设置中隔离出来&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>w3cschool官网 - 编程狮&#xff0c;随时随地学编程</title> </head&g…

uni-app利用chooseImage方法封装一个图片选择组件

效果如图&#xff1a; 可以预览 长按可删除 可以设置最多上传数量 这里封装的组件有个MaxNumber &#xff0c;number类型&#xff0c;用的时候在父组件传就行了&#xff0c;这里默认给的8 废话不多说直接上代码 封装好了之后我们用的时候只需要引入直接用就行 <templat…

TypeScript学习笔记之二(高级类型)

文章目录一、TypeScript高级类型1.1 class类1.2 class继承1.3 class类成员可见性1.4 readonly1.5 类型兼容性1.5.1 对象之间的类型兼容性1.5.2 接口之间类型兼容性1.5.3 函数之间类型兼容性1.6 交叉类型1.7 交叉类型(&)和继承(extends)的对比二、泛型2.1 泛型约束--指定更具…

考虑大规模电动汽车接入电网的双层优化调度策略【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

蓝奥声核心技术分享——基于物联网的能源监测数据采集技术

1.技术背景 基于物联网的能源监测数据采集技术主要解决物联网能源监测节点面向目标对象以协同方式进行能源监测数据采集的问题&#xff0c;属于蓝奥声核心技术--边缘协同感知(EICS&#xff09;技术的关键支撑性技术之一。该项技术涉及无线物联网边缘智能与测控的技术领域&…