C++小白实习日记——Pollnet,Efvi,UDP,数据类型转换(上)

上周主要是熟悉了一下公司内部一些自定义结构体对应的数据类型,要求:读取文件,将文件中数据转化为定义的结构体中的数据类型,按照时间进行排序,用UDP发送数据;在另一台服务器上接收数据,按照定义好的数据结构存储传输过来的UDP包

一,发送端设计

1,读取文件并转换成指定数据结构

包括但不限于,读取文件,获取表头,打印表头,之前的同事留下的代码将表头标签用列表存储,我做了一个字段到索引的映射,就是读取第一行作为表头

    const std::string _csvPath = "文件.csv";// 检查文件是否存在if (!fileExists(_csvPath)) {std::cerr << "CSV file does not exist: " << _csvPath << std::endl;return false;}// 初始化 CsvReaderCsvReader reader;try {reader.load_from_file(_csvPath.c_str());} catch (const std::exception& e) {std::cerr << "Error loading CSV file: " << e.what() << std::endl;return false;}// 获取表头信息std::vector<std::string> header;if (!reader.next_row(header)) {std::cerr << "CSV file is empty or cannot read the header row." << std::endl;return false;}// 打印表头内容std::cout << "Table Header: ";for (const auto& field : header) {std::cout << field << " ";}std::cout << std::endl;// 存储字段的有序顺序和映射std::vector<std::string> orderedFields;std::unordered_map<std::string, int> fieldMap;// 构建字段映射for (size_t i = 0; i < header.size(); ++i) {orderedFields.push_back(header[i]);fieldMap[header[i]] = static_cast<int>(i);}// 打印映射结果printFieldMap(orderedFields, fieldMap);

其中读取文件以及读表用了一个封装了的类CSVReader

// CSV 文件解析类
class CsvReader {
public:CsvReader() = default;void load_from_file(const char* filepath) {file_.open(filepath);if (!file_.is_open()) {throw std::runtime_error("Failed to open CSV file.");}}bool next_row(std::vector<std::string>& row) {row.clear();std::string line;if (std::getline(file_, line)) {std::stringstream line_stream(line);std::string cell;while (std::getline(line_stream, cell, ',')) {row.push_back(cell);}return true;}return false;}private:std::ifstream file_;
};

这个类依赖于 std::ifstream。std::ifstream是 C++ 标准库中的一个输入流类,用于从文件读取数据。std::getline是标准中的函数。

  • next_row 方法每次被调用时会从文件中读取一行数据(std::getline(file_, line))。如果成功读取到一行数据,函数会继续处理这行数据。

  • 使用 std::stringstream 来解析这一行数据,通过 std::getline(line_stream, cell, ',') 按照逗号(,) 将行数据分割成单独的单元(单元格),然后将每个单元(cell)加入到 row 向量中。

字段映射主要是用std::vector<std::string> orderedFields; std::unordered_map<std::string, int> fieldMap;

  • orderedFields.push_back(header[i]) 将每个字段名称按顺序存储到 orderedFields 中,这样 orderedFields 就是一个字段名的有序集合。

  • fieldMap[header[i]] = static_cast<int>(i) 将每个字段名称与其索引(即列的位置)存储到 fieldMap 映射中,这样 fieldMap 就是一个字段名到索引值的映射。

其中fieldMap是无序的,但是他里面的键值对有序号。

打印键值对:

// 打印字段映射
void printFieldMap(const std::vector<std::string>& orderedFields, const std::unordered_map<std::string, int>& fieldMap) {std::cout << "Field Map (Ordered):" << std::endl;for (const auto& field : orderedFields) {std::cout << "  " << field << " -> " << fieldMap.at(field) << std::endl;}
}

at(field)std::unordered_map 类的成员函数,它的作用是 根据键(field)查找并返回对应的值,如果找不到该键,它会抛出一个 std::out_of_range 异常。

2,转换数据类型

std::vector<PSILEV2API::PSILev2MarketDataField> marketDataList; // 用于存储所有行的 MarketData// 解析数据行PSILEV2API::PSILev2MarketDataField marketData{};// 读取数据行std::vector<std::string> row;while (reader.next_row(row)) {std::cout << "Row: ";for (const auto &value: row) {std::cout << value << " ";}std::cout << std::endl;for (const auto &field: orderedFields) {auto it = fieldMap.find(field);if (it == fieldMap.end() || it->second >= static_cast<int>(row.size())) {continue;}std::string Field = field;Field.erase(std::remove(Field.begin(), Field.end(), '\"'), Field.end()); // 去掉引号Field.erase(0, Field.find_first_not_of(" \t\n\r"));Field.erase(Field.find_last_not_of(" \t\n\r") + 1);if (Field == "datetime") {std::string datetime = row[it->second];marketData.DataTimeStamp = convertToTimeStamp(datetime);std::cout << "DataTimeStamp: " << marketData.DataTimeStamp << std::endl;} }marketDataList.push_back(marketData); // 将每一行的 MarketData 对象存入列表}

逐行读取数据,将数据中的field与想要的标签做对比,符合要求后将其转化为需要的结构体中的数据,添加到结构体中,然后将转化后的结构体逐行push_back到marketData的列表中。

主要分为三部分:1)检查表头字段是不是在无序索引filedMap中

for (const auto &field: orderedFields)

这一行是一个范围基 for 循环,用来遍历 orderedFields 中的每一个 field(字段名称)。orderedFields 是一个存储字段名称(如 "datetime", "price" 等)的 std::vector<std::string>

auto it = fieldMap.find(field)

这一行在 fieldMap 中查找当前字段(field)的索引。

  • fieldMap 是一个 std::unordered_map<std::string, int>,用来存储字段名称到索引的映射关系。

  • fieldMap.find(field) 会返回一个迭代器 it,指向字段名称 fieldfieldMap 中的元素。如果找到了该字段名,它会返回对应的键值对的迭代器;如果没有找到,它会返回 fieldMap.end()

if (it == fieldMap.end() || it->second >= static_cast<int>(row.size())) { continue; }

这部分是一个条件判断,用来检查是否可以有效地从 row 中提取数据。

  • it == fieldMap.end():表示在 fieldMap 中找不到当前字段名称 field。如果找不到字段名,则跳过当前字段的处理。

  • it->second >= static_cast<int>(row.size())it->secondfieldMap 中该字段对应的索引(字段的位置)。row.size() 是当前行的列数。如果该索引超出了当前行的列数(即字段位置无效),则跳过当前字段的处理。

  • continue:如果满足以上任一条件,continue 会跳过当前循环的剩余部分,继续处理下一个字段。

2)将表头字段的前后空格,斜杠等去掉

Field.erase(std::remove(Field.begin(), Field.end(), '\"'), Field.end());
  • std::remove 是一个标准库算法,它会在范围 [Field.begin(), Field.end()) 内删除所有的字符 '\"',即所有的双引号字符(" ")。但是,它并不会真正从容器中删除元素,而是将这些元素“移动”到容器的末尾,返回一个新的迭代器,指向新容器的有效元素区域的末尾。

    例如,给定字符串 "\"example\""std::remove 会将所有的双引号字符移到字符串的末尾,并返回一个新的“有效范围”。

  • Field.erase 将这个新的范围以外的部分(即所有的双引号)实际删除,最后得到的字符串将不再包含双引号。

    例如,输入 "\"example\"",经过这行代码后,Field 会变成 "example"

Field.erase(0, Field.find_first_not_of(" \t\n\r"));
  • Field.find_first_not_of(" \t\n\r"):这个函数查找字符串中第一个不是空格、制表符(Tab)、换行符(\n)、回车符(\r)的字符的位置。

    • 如果 Field 开头有空格、Tab、换行符或回车符,它会返回第一个非空白字符的位置。

    • 如果没有找到空白字符(即字符串没有前导空格),则返回字符串的起始位置(0)。

  • Field.erase(0, ...):这一行的作用是删除 Field 字符串中的前导空白字符。它会从位置 0 开始删除,删除的长度是从 0find_first_not_of 找到的位置,实际上就是删除了所有前导的空格、制表符等。

    例如,如果 Field" example", 则 Field.find_first_not_of(" \t\n\r") 返回 3,表示第一个非空白字符的位置是 'e'。然后,erase 会删除前面的 3 个空格,Field 变成 "example"

 Field.erase(Field.find_last_not_of(" \t\n\r") + 1);
  • Field.find_last_not_of(" \t\n\r"):这个函数查找字符串中最后一个不是空格、制表符、换行符或回车符的字符的位置。

    • 如果 Field 末尾有空白字符,它会返回最后一个非空白字符的位置。

    • 如果没有找到空白字符(即字符串没有尾随空格),则返回字符串的最后一个字符的位置。

  • Field.erase(... + 1):这行代码的作用是删除 Field 字符串末尾的空白字符。通过 find_last_not_of 找到最后一个非空白字符的位置,并删除从该位置到字符串末尾的所有字符,实际上就是去掉尾部的空白字符。

    例如,如果 Field"example ", 则 Field.find_last_not_of(" \t\n\r") 返回 7,表示最后一个非空白字符的位置是 'e'。然后,erase 会删除从位置 8 开始到字符串末尾的空格,Field 变成 "example"

3)判断如果当前表头符合转换要求,将数据赋值给std::string,然后转换数据类型

PSILEV2API::TTORATstpTimeStampType convertToTimeStamp(const std::string& datetime) {// 查找时间部分(假设格式为 "YYYY/MM/DD HH:MM:SS")size_t time_pos = datetime.find(' ');if (time_pos == std::string::npos) {throw std::invalid_argument("Invalid datetime format");}// 提取时间部分std::string time_part = datetime.substr(time_pos + 1);// 分割时、分、秒int hours, minutes, seconds;char delimiter;std::istringstream time_stream(time_part);time_stream >> hours >> delimiter >> minutes >> delimiter >> seconds;if (time_stream.fail()) {throw std::invalid_argument("Invalid time format");}// 转换为毫秒格式的整数:HHMMSS000return (hours * 10000000) + (minutes * 100000) + (seconds * 1000);
}

这里用了std::istringstream是一个标准库中的流类

  • 流的输入:当使用 >> 操作符时,std::istringstream 会逐个字符地读取 time_part 字符串,直到它成功地将数据解析为一个变量的类型。例如,当它试图将 "14" 解析为 hours 时,它成功地解析了数字 14。

  • 分隔符的“消耗”:流会自动跳过空格或其他分隔符(例如冒号)。这些分隔符并不直接存储在变量中,而是被“消耗”掉,用来区分不同的数据段。

  • 多个变量解析:可以通过连续使用 >> 操作符来解析多个值,每个操作符都会从流中提取出下一个数据,直到整个字符串被解析完或者遇到错误。

3,UDP发送

// 创建EfviUdpSender实例并初始化EfviUdpSender udpSender;udpSender.init("enp8s0f1", "10.100.100.162", 12345, "10.100.100.161", 12346);// 遍历 marketDataList,序列化并发送每一行数据for (size_t i = 0; i < marketDataList.size(); ++i) {const auto &marketData = marketDataList[i];// 序列化数据size_t dataSize = sizeof(marketData);std::vector<char> serializedData(dataSize);std::memcpy(serializedData.data(), &marketData, dataSize);// 打印字节流的十六进制表示for (size_t j = 0; j < dataSize; ++j) {printf("%02X ", static_cast<unsigned char>(serializedData[j]));}printf("\n");// 通过 UDP 发送数据包if (!udpSender.write(serializedData.data(), dataSize)) {std::cerr << "Failed to send market data for row " << i << std::endl;continue;}std::cout << "Market data for row " << i << " sent via UDP!" << std::endl;// 控制发送频率(例如每秒发送一次)std::this_thread::sleep_for(std::chrono::seconds(1));

 先初始化EfviUdpSender udpSender,其中

  • "enp8s0f1":网络接口的名称,表示通过该网络接口进行数据发送。enp8s0f1 是一个 Linux 下的网络接口名称。

  • "10.100.100.162", 12345:目标 IP 地址和端口号,表示将数据发送到该地址和端口。

  • "10.100.100.161", 12346:源 IP 地址和端口号,表示从该地址和端口发送数据。

用for循环遍历marketDataList并序列化数据。

1. std::memcpy 函数

std::memcpy 是 C++ 标准库中的一个函数,定义在 <cstring> 头文件中。它用于将内存区域的内容复制到另一个内存区域。函数原型如下:

void* memcpy(void* dest, const void* src, std::size_t count);
  • dest:目标内存地址,即要复制到的位置。

  • src:源内存地址,即要从哪里复制数据。

  • count:要复制的字节数。

在 C++ 中,结构体和类对象的数据通常以二进制格式存储。直接通过 std::memcpy 进行内存拷贝是一种高效的方法,可以避免逐个字段的序列化。尤其是在网络通信中,发送和接收的数据通常是字节流,而不是结构化数据。因此,使用 std::memcpy 将结构体数据转换为字节流,并直接发送是一种常见的做法

2. static_cast强制转换

  • 这里使用 static_castserializedData[j] 强制转换为 unsigned char 类型。这样做的原因是 printf 格式化输出时,默认处理的 char 类型(尤其是有符号 char)可能会导致负数问题,因为 char 可以是有符号的,取值范围为 -128127

  • 强制转换为 unsigned char 类型可以确保输出为 0 到 255 的有效字节值

3."%02X "

  • printf 用于格式化输出,第一个参数 "%02X " 是格式字符串,告诉 printf 如何输出数据:

    • %02X:以十六进制格式打印整数,且每个数字至少占 2 个字符宽度。如果数字是 1 位(比如 0x9),则会在前面补充 0,使其变成 09

    • X 表示输出大写的十六进制字母(A-F)。

    • 02 表示输出的最小宽度为 2 个字符,如果一个字节的值小于 16(即 0x0 到 0xF),则会在前面补充一个零以满足 2 位宽度。

    • " 用于在输出中打印空格,确保每个字节后面都有一个空格,使输出更加易读。

    所以,如果 serializedData[j] 的值是 255,那么输出将是 FF,如果它是 9,输出将是 09

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

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

相关文章

路由引入问题(双点双向路由回馈问题)

简介 总所周知&#xff0c;路由引入import又称路由重分发redistribute&#xff0c;为了解决不同路由协议进程间路由信息不互通而使用的技术&#xff0c;由于不同路由协议的算法、机制、开销等因素的差异&#xff0c;它们之间无法直接交换路由信息。因此&#xff0c;路由引入技…

26. Three.js案例-自定义多面体

26. Three.js案例-自定义多面体 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染场景的主要类。它支持 WebGL 渲染&#xff0c;并提供了多种配置选项。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&…

【在Linux世界中追寻伟大的One Piece】HTTP Session

目录 1 -> 引入HTTP Session 1.1 -> 定义 1.2 -> 工作原理 1.3 -> 安全性 1.4 -> 超时和失效 1.5 -> 用途 2 -> 模拟session行为 3 -> 实验测试session 1 -> 引入HTTP Session 1.1 -> 定义 HTTP Session是服务器用来跟踪用户与服务器交…

Docker-Dockerfile、registry

Dockerfile 一、概述 1、commit的局限 很容易制作简单的镜像&#xff0c;但碰到复杂的情况就十分不方便&#xff0c;例如碰到下面的情况&#xff1a; 需要设置默认的启动命令需要设置环境变量需要指定镜像开放某些特定的端口 2、Dockerfile是什么 Dockerfile是一种更强大的镜…

蓝桥杯刷题——day1

蓝桥杯刷题——day1 题目一题干题目解析代码 题目二题干题目解析代码 题目一 题干 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。本题中&#xff0c;将空字符串定义为有效的 回文串 。 题目链接&a…

【多模态文档智能】OCR-free感知多模态大模型技术链路及训练数据细节

目前的一些多模态大模型的工作倾向于使用MLLM进行推理任务&#xff0c;然而&#xff0c;纯OCR任务偏向于模型的感知能力&#xff0c;对于文档场景&#xff0c;由于文字密度较高&#xff0c;现有方法往往通过增加图像token的数量来提升性能。这种策略在增加新的语言时&#xff0…

如何在 Ubuntu 22.04 上使用 Fail2Ban 保护 SSH

前言 SSH&#xff0c;这玩意儿&#xff0c;简直是连接云服务器的标配。它不仅好用&#xff0c;还很灵活。新的加密技术出来&#xff0c;它也能跟着升级&#xff0c;保证核心协议的安全。但是&#xff0c;再牛的协议和软件&#xff0c;也都有可能被攻破。SSH 在网上用得这么广&…

供应链系统设计-中台系统设计系列(三)- 好中台的标准之稳定原则

概述 在上一篇供应链系统设计-中台系统设计系列&#xff08;二&#xff09;- 好中台的标准之复用原则中&#xff0c;我们以复用原则为主&#xff0c;讨论了以下3点&#xff1a; 前台业务效率提升&#xff1a;好的中台能够显著提高前台业务的效率&#xff0c;通过将前台业务中通…

CTF 攻防世界 Web: FlatScience write-up

题目名称-FlatScience 网址 index 目录中没有发现提示信息&#xff0c;链接会跳转到论文。 目前没有发现有用信息&#xff0c;尝试目录扫描。 目录扫描 注意到存在 robots.txt 和 login.php。 访问 robots.txt 这里表明还存在 admin.php admin.php 分析 在这里尝试一些 sql…

axios请求拦截器和响应拦截器,封装naive-ui的 Loading Bar加载条和useMessage消息提示

接之前的博客设计从0开始边做边学&#xff0c;用vue和python做一个博客&#xff0c;非规范化项目&#xff0c;怎么简单怎么弄&#xff0c;跑的起来有啥毛病解决啥毛病&#xff08;三&#xff09;&#xff0c;目前已经完成了基本的功能demo&#xff0c;但是请求接口不可能每个页…

Blue Ocean 在Jenkins上创建Pipeline使用详解

BlueOcean是Jenkins的一个插件,它提供了一套可视化操作界面来帮助用户创建、编辑Pipeline任务。以下是对BlueOcean中Pipeline操作的详细解释: 一、安装与启动BlueOcean 安装:在Jenkins的“系统管理”->“插件管理”->“可选插件”中搜索“BlueOcean”,然后点击“Ins…

opencv——识别图片颜色并绘制轮廓

图像边缘检测 本实验要用到Canny算法&#xff0c;Canny边缘检测方法常被誉为边缘检测的最优方法。 首先&#xff0c;Canny算法的输入端应为图像的二值化结果&#xff0c;接收到二值化图像后&#xff0c;需要按照如下步骤进行&#xff1a; 高斯滤波。计算图像的梯度和方向。非极…

Python中的异步编程:从基础到实践

在现代编程中,异步编程已经成为提高程序性能和响应能力的重要手段。Python,作为一种动态、解释型的高级编程语言,提供了多种异步编程的解决方案。本文将从Python异步编程的基础知识出发,逐步深入到实际应用中,帮助读者理解和掌握这一技术。 1. 异步编程简介 异步编程是一…

基础库urllib的使用

学习爬虫&#xff0c;其基本的操作便是模拟浏览器向服务器发出请求&#xff0c;那么我们需要从哪个地方做起呢?请求需要我们自己构造吗?我们需要关心请求这个数据结构怎么实现吗?需要了解 HTTP、TCP、IP层的网络传输通信吗?需要知道服务器如何响应以及响应的原理吗? 可能…

剑指Offer|day4 LCR 004. 只出现一次的数字 II

LCR 004. 只出现一次的数字 II 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [2,2,3,2] 输出&#xff1a;3提示&#xff1a…

【Linux】poll函数

poll和select的区别不大&#xff0c;主要是poll没有连接数限制&#xff0c;因为它用的链表实现 #include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd {int fd; //要监控的文件描述符,如果fd为-1, 表示内核不再监控short…

Mysql学习笔记之SQL-1

上篇文章我们介绍了Mysql的安装&#xff0c;这篇文章我们介绍Mysql的操作语言SQL 1. 简介 sql全称&#xff08;Structured Query Language&#xff09;是结构化查询语言&#xff0c;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准 2. sql分类 …

埃隆马斯克X-AI发布Grok-2大模型,快来体验~

引言 近年来&#xff0c;人工智能技术的快速发展推动了大语言模型的广泛应用。无论是日常生活中的智能助手&#xff0c;还是行业中的自动化解决方案&#xff0c;大语言模型都扮演着越来越重要的角色。2024年&#xff0c;X-AI推出了新一代的大模型——Grok-2&#xff0c;这款模…

PostgreSQL的学习心得和知识总结(一百六十三)|深入理解PostgreSQL数据库之 GUC参数compute_query_id 的使用和实现

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

基于自然的解决方案的学习

根据世界自然保护联盟&#xff08;IUCN&#xff09;的定义&#xff1a; “基于自然的解决方案&#xff08;NbS&#xff09;是保护、可持续管理和恢复自然的和被改变的生态系统的行动&#xff0c;能有效和适应性地应对社会挑战&#xff0c;同时提供人类福祉和生物多样性效益。”…