1. 基本框架:

前面我们已近完成了,基于UDP协议的网络通信,但是我们服务器接收到来自客户端的信息即字符串时只是进行了简单的发送会客户端和在日志中回显打印,并没有实际的业务服务。那么接下来,我们就设计一个字典翻译的模块,然后让网络通信模块字典翻译模块进行解耦。也就是说,网络模块就负责进行通信即可,处理来自客户端的数据的任务交给字典翻译模块!这样两个模块之间就没有强耦合了!!

2. 走通基本逻辑:

很简单,我们在服务器内部定义一个包装器类型func_t,这个包装器可以接受所有可调用对象,我们在C++11当中就已经说过了。因为我们现在把客户端发送的字符传当做英文单词,我们希望给用户返回一个汉语。所以我们包装器的返回值和参数都为string。

这里简单设计一下字典模块细节先不填。

 下面是我找的字典配置文件,放在最后了。

下面就是我们模块和模块之间进行解耦的原理了,本质就是用函数来进行回调,类用来封装模块,完成任务!! 

下面来debug一下,看看逻辑能不能走通~~

OK啊,也是没有问题的 。那接下来就可以放心的完成字典翻译模块啦~~

2. 完善翻译逻辑【dict.hpp】

#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include "log.hpp"using namespace log_module;const std::string default_dict = "./dict.txt";
const std::string sep = ": "; // 分隔符class dict
{
public:dict(const std::string &path = default_dict) : _path(path){}// 加载配置文件bool down_load(){// 打开配置文件std::ifstream in(_path);if (!in.is_open()){LOG(log_level::Error) << "打开字典配置文件失败!" << " 路径" << _path;return false;}// 读取配置文件std::string line;while (std::getline(in, line)){// apple: 苹果 [[9]]auto pos = line.find(sep);if (pos == std::string::npos) // 没有找到分隔符{LOG(log_level::Warning) << "加载" << line << "错误!" << " 跳过";continue;}std::string English = line.substr(0, pos);std::string Chinese = line.substr(pos + sep.size());if (English.empty() || Chinese.empty()){LOG(log_level::Warning) << "加载数据" << line << "无效!" << " 跳过";continue;}_map.insert(std::make_pair(English, Chinese));LOG(log_level::Debug) << "加载" << line << "成功";}in.close();return true;}// translatestd::string translate(const std::string &dict){// LOG(log_level::Debug) << "走到了翻译逻辑";auto tmp = _map.find(dict);if (tmp == _map.end()){return "None";}return tmp->second;}~dict(){}private:std::string _path;                                 // 配置文件的路径std::unordered_map<std::string, std::string> _map; // 从配置文件加载的字典
};

测试成功!! 

3. 封装InetAddr

现在,如果我还有一个需求,就是我们想在服务端翻译的时候获取是哪个IP地址,哪个端口号像我们发送的请求。其实,我们在服务端从网络上获取客户端信息时就拿到了该数据只是没有做处理和记录下来而已

接下来,我们就来封装一个InetAddr类来对这些数据做处理,并且我们把处理之后的结果要传给translate函数,一旦翻译一条数据后我们就用日志回显出来。

InetAddr

class InetAddr
{
public:InetAddr(const sockaddr_in &peer) : _peer(peer){_port = ntohs(_port);            // 端口号_ip = inet_ntoa(_peer.sin_addr); // IP地址->点分十进制}uint16_t port(){return _port;}std::string IP(){return _ip;}~InetAddr(){}private:struct sockaddr_in _peer;uint16_t _port;std::string _ip;
};

 具体的回调逻辑:

测试结果我们也看到了本地回环的IP和随机绑定的端口号。 

至此,我们基于字典翻译服务的UDP网络通信就简单设计完成了。

4. 源码

server.cc

#include <iostream>
#include "server.hpp"
#include <memory>
#include "dict.hpp"int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage: ./server port" << std::endl;return 1;}using_screen_strategy();// std::string ip = argv[1];uint16_t port = std::stoi(argv[1]);dict d;d.down_load();std::unique_ptr<server> p1 = std::make_unique<server>(port,[&d](const std::string& english,InetAddr& client){return d.translate(english,client);});p1->init();p1->start();return 0;
}

server.hpp

#pragma once#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>
#include <functional>
#include "inet_addr.hpp"using namespace log_module;const int default_sockfd = -1;using func_t = std::function<std::string(const std::string &, InetAddr &)>;class server
{
public:server(uint16_t port, func_t func): _sockfd(default_sockfd),_func(func),//   _ip(ip),_port(port),_isrunning(false){}~server(){}// 服务端初始化和启动void init(){// 创建套接字->打开网络文件_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(log_level::Fatal) << "socket 创建失败!";exit(1);}LOG(log_level::Info) << "socket 创建成功!" << _sockfd;// 填充sockaddr_in结构体struct sockaddr_in local;// 数据清空bzero(&local, sizeof(local));// 数据将来一定会发送到网络// 端口号和IP地址 本地格式->网络序列local.sin_family = AF_INET;    // 协议家族local.sin_port = htons(_port); // 端口号// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // IP地址local.sin_addr.s_addr = INADDR_ANY; // IP地址任意绑定int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(log_level::Fatal) << "bind 失败!";exit(2);}LOG(log_level::Info) << "bind 成功!" << _sockfd;}void start(){_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in local;socklen_t len = sizeof(local);// 收信息ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&local, &len);if (n > 0) // 成功是收到信息{// 处理从网络获取的信息后续交给翻译InetAddr client(local);buffer[n] = 0;// 讲数据回调给外层的函数进行处理,接收返回结果即可std::string ret = _func(buffer, client);// 将处理后的信息发给客户端ssize_t m = sendto(_sockfd, ret.c_str(), ret.size(), 0, (struct sockaddr *)&local, sizeof(local));}}}private:int _sockfd;// std::string _ip; // IP地址用的字符串风格uint16_t _port; // 端口号bool _isrunning;func_t _func;
};

client.cc

#include <iostream>
#include "server.hpp"
#include <memory>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>// 客户端知道服务器的IP地址和端口号
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage: ./server IP port" << std::endl;return 1;}using_screen_strategy();std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);// 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(log_level::Fatal) << "socket 创建失败!";exit(1);}LOG(log_level::Info) << "socket 创建成功!" << sockfd;// 客户端不需要显示的bind自己的IP地址和端口号// 但是,在首次发送信息的时候,系统会自动绑定IP地址和端口号,端口号是系统随机分配的// 为什么要这样做呢??首先,端口号原则上来说只能有一个进程占有,我们的一台主机上可以同时启动了多个进程// 比如我先后打开了抖音和快手两个APP,如果这两个进程都显示的绑定系统的端口号,有没有可能这连个进程所绑定// 端口号是一样的呢??所以,为了避免端口号冲突,系统会为我们分配随机未使用的端口号自动绑定。// 填写服务器信息sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());// 收发送消息while (true){std::string input;std::cout << "请输入#";std::getline(std::cin, input);// 发送消息int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&local, sizeof(local));// 接收消息【有可能连接多个服务器,所以要接收服务器端口号】char buffer[1024];sockaddr_in peer;bzero(&peer, 0);socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;std::cout << "------------------------------\n";}}return 0;
}

dict.hpp

#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include "log.hpp"using namespace log_module;const std::string default_dict = "./dict.txt";
const std::string sep = ": "; // 分隔符class dict
{
public:dict(const std::string &path = default_dict) : _path(path){}// 加载配置文件bool down_load(){// 打开配置文件std::ifstream in(_path);if (!in.is_open()){LOG(log_level::Error) << "打开字典配置文件失败!" << " 路径" << _path;return false;}// 读取配置文件std::string line;while (std::getline(in, line)){// apple: 苹果 [[9]]auto pos = line.find(sep);if (pos == std::string::npos) // 没有找到分隔符{LOG(log_level::Warning) << "加载" << line << "错误!" << " 跳过";continue;}std::string English = line.substr(0, pos);std::string Chinese = line.substr(pos + sep.size());if (English.empty() || Chinese.empty()){LOG(log_level::Warning) << "加载数据" << line << "无效!" << " 跳过";continue;}_map.insert(std::make_pair(English, Chinese));LOG(log_level::Debug) << "加载" << line << "成功";}in.close();return true;}// translatestd::string translate(const std::string &dict, InetAddr &peer){// LOG(log_level::Debug) << "走到了翻译逻辑";auto tmp = _map.find(dict);if (tmp == _map.end()){return "None";}LOG(log_level::Info) << dict << "->" << tmp->second << "[" << peer.IP() << " : " << peer.port() << "]";return tmp->second;}~dict(){}private:std::string _path;                                 // 配置文件的路径std::unordered_map<std::string, std::string> _map; // 从配置文件加载的字典
};

InetAddr.hpp

#pragma once#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include "log.hpp"
#include <arpa/inet.h>
#include <functional>class InetAddr
{
public:InetAddr(const sockaddr_in &peer) : _peer(peer){_port = ntohs(_port);            // 端口号_ip = inet_ntoa(_peer.sin_addr); // IP地址->点分十进制}uint16_t port(){return _port;}std::string IP(){return _ip;}~InetAddr(){}private:struct sockaddr_in _peer;uint16_t _port;std::string _ip;
};

dict.txt 

apple: 苹果 [[9]]
banana: 香蕉 [[9]]
divide: 分割 [[3]]
closed-door policy: 闭关自守 [[11]]
keep healthy: 保持健康 [[11]]
he: 他 [[9]]
countries: 国家 [[9]]
exam: 考试 [[8]]
lab: 实验室 [[8]]
UNESCO: 联合国教科文组织 [[8]]
smog: 烟雾 [[8]]
smoke: 烟 [[8]]
fog: 雾 [[8]]
carelessly: 粗心地 [[8]]
re-export: 再出口 [[8]]
undoubtedly: 无疑地 [[8]]
Vast lawns: 宽阔的草坪 [[7]]
gigantic trees: 参天大树 [[7]]
foliage: 树叶 [[7]]
extensive: 广阔的 [[7]]
sheets of vivid green: 鲜艳的绿绒似的毡毯 [[7]]
clumps of gigantic trees: 数株巨树 [[7]]
heaping up: 聚集 [[7]]
rich piles: 浓密的堆叠 [[7]]
here and there: 这里和那里 [[7]]
fancying himself: 自以为 [[5]]
so great: 非常了不起 [[5]]
walking: 走 [[5]]
fancy: 想象 [[5]]
great: 伟大的 [[5]]
self: 自己 [[5]]
East: 东 [[5]]
West: 西 [[5]]
mouse: 老鼠 [[9]]
photos: 照片 [[9]]
boy: 男孩 [[9]]
swim: 游泳 [[9]]
three: 三 [[9]] 

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

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

相关文章

Quality Control II: Trimming (二):BBDuk

参考&#xff1a;BBDuk Guide - Archive 在我们了解了如何使用trimmomatic之后&#xff0c;我们开始进一步了解另外一种trim工具BBDuk 首先小编要声明&#xff1a;如果想要完全掌握一个工具是需要较长时间的钻研和学习的&#xff0c;这里呢只是提供BBDuk处理数据的基本逻辑和…

AlmaLinux8 平替 manylinux_2_28-python 的 GPG密钥管理、安装 cuda sdk

0. 下载 AlmaLinux 8 docker 镜像 https://hub.docker.com/r/almalinux/8-base/tags 下载镜像&#xff1a; sudo docker pull almalinux/8-base:8.4 创建一个容器&#xff1a; sudo docker run --gpus all -it --name cudaq_src_py_LHL_06 -v /home/hanmeimei/big…

BM1684X平台:Qwen-2-5-VL图像/视频识别应用

一、 简介 Qwen-2-5-VL 是阿里巴巴通义千问团队推出的多模态大语言模型&#xff08;MLLM&#xff09;&#xff0c;属于 Qwen-2 系列模型的一部分&#xff0c;支持视觉&#xff08;Vision&#xff09;与语言&#xff08;Language&#xff09;的多模态交互。 1、特性 动态分辨…

前端项目工程化配置webpack与vite

webpack与vite一、了解 webpack入口(entry)输出(output)loader插件(plugin)模式(mode)二、项目中使用webpackvue项目react项目三、了解vite构建选项&#xff08;build&#xff09;模块解析&#xff08;Resolve&#xff09;模块处理&#xff08;Module&#xff09;服务器选项&am…

机器学习(3):KNN算法-分类

一、KNN算法 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;,根据K个邻居样本的类别来判断当前样本的类别&#xff1b;如果一个样本在特征空间中的k个最相似(最邻近)样本中的大多数属于某个类别&#xff0c;则该类本也属于这个类别。一些距离&…

Redis Windows迁移方案与测试

我想将开源软件Redis的主程序和附属程序迁移到Windows平台&#xff0c;目前它只能在Linux上运行&#xff0c;让它可以在Windows 11和Windows Server 2025上运行&#xff0c;这需要考虑Linux操作系统和Windows操作系统的差异&#xff0c;请列举出将Redis在Linux系统上运行的GCC的…

信息安全概述--实验总结

数据链路层--ARP欺骗ARP欺骗原理XP2要与XP3通信&#xff0c;要发送ARP请求&#xff0c;询问XP3的MAC地址kali冒充XP3持续给XP2发送ARP应答&#xff0c;XP2会以为收到的MAC地址是XP3的&#xff0c;实际是kali的之后XP2发送的数据都是发给kali的如果说XP2需要想要访问互联网&…

【Electron】打包后图标不变问题,图标问题

windows上图标未更换。图标已经换了&#xff0c;但新打出的包或是安装后的 exe 图标没有更换。这个时候可以右击你的exe或是安装包点属性&#xff0c;看看图标是否正常&#xff0c;如果这里的图标正常&#xff0c;那其实就是成功的了。主要原因是因为 windows 图标缓存机制导致…

单词拆分 II

题目&#xff1a;思考&#xff1a; 本质上和单词拆分1没什么区别单词拆分1是问能不能拆单词拆分2是问把所有拆的方案列出来要列出所有方案&#xff0c;采用字典树回溯 实现&#xff1a; class Node { public:vector<Node*> check;bool isEnd;Node(int num){for (int i0;i…

国产三防平板电脑是什么?三防平板推荐

国产三防平板电脑&#xff0c;专为应对极端工作环境而生。这类设备集防水、防尘、防摔三大防护性能于一体&#xff0c;通过IP67/IP68防护认证及MIL-STD-810军规标准测试&#xff0c;能在建筑工地、油田勘探、应急救援等恶劣场景中稳定运行。其核心价值在于将消费级平板的智能体…

优思学院|什么是精益生产管理?原则与方法详述

在企业经营中&#xff0c;「利润&#xff1d;价格&#xff0d;成本」这条公式可谓家喻户晓。传统的成本思维通常认为价格由公司设定&#xff0c;而成本则是难以撼动的既定事实。然而&#xff0c;随着市场经济与自由定价机制的成熟&#xff0c;企业逐渐意识到——价格其实是由市…

【银行测试】银行票据项目业务+票据测试点分析(四)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、提示付款 功能…

基于华为开发者空间的Open WebUI数据分析与可视化实战

1 概述 1.1 案例介绍 本案例演示如何在华为开发者空间云主机上搭建Open WebUI环境&#xff0c;结合DeepSeek-R1模型进行数据分析、统计建模、数据可视化和业务洞察挖掘等实际数据科学任务。 1.2 适用对象 数据分析师业务分析师数据科学工程师市场研究人员统计学专业学生 1…

【HZ-T536开发板免费体验】Cangjie Magic调用视觉语言大模型(VLM)真香,是不是可以没有YOLO和OCR了?

目录 引言 编写视觉语言大模型&#xff08;VLM&#xff09;程序 交叉编译Cangjie Magic到T536开发板 对cjpm.toml文件的修改 stdx库的配置 拷贝libsecurec.so到cangjie的库文件中 开始交叉编译 部署到开发板 拷贝所需要的库文件 安装curl 运行程序 结束语 本文首发…

最长连续序列(每天刷力扣hot100系列)

目录 题目介绍&#xff1a; 哈希表法&#xff1a; 复杂度分析&#xff1a; 思路分析&#xff1a; unordered_set 和 unordered_map的比较&#xff1a; 1. 核心区别 2. 使用场景 3. 在本题中的选择 4. 性能对比 5. 成员函数差异 unordered_table.begin()函数是返回的键…

国标渠道研究:专业为渠道策略提供数据支持(渠道调研)

北京国标市场调查有限公司是一家专业的市场调查公司&#xff0c;&#xff08;线上问卷调查&#xff09;&#xff08;第三方市场咨询&#xff09;&#xff08;消费者调查研究&#xff09;专注于为企业提供全方位的渠道研究服务。服务范围包括渠道策略研究、渠道销售数据分析和渠…

深入理解 C 语言中的拷贝函数

目录1. C 语言中的主要拷贝函数2. strcpy&#xff1a;字符串拷贝函数签名示例局限性3. strncpy&#xff1a;指定长度的字符串拷贝函数签名示例局限性4. memcpy&#xff1a;通用内存拷贝函数签名示例优势局限性5. memmove&#xff1a;支持重叠内存拷贝函数签名示例优势局限性6. …

主数据变更流程

主数据&#xff08;如客户、供应商、产品等&#xff09;的变更流程&#xff08;新增、更新、停用等&#xff09;是主数据管理&#xff08;MDM&#xff09;的核心环节&#xff0c;其设计需兼顾数据质量&#xff08;准确性、一致性&#xff09;、业务合规&#xff08;审批权限、审…

VUE2 学习笔记 合集

​​​​​​​VUE2 学习笔记1 VUE特点、开发者工具、入门Demo-CSDN博客 VUE2 学习笔记2 数据绑定、数据代理、MVVM_vue2的数据绑定-CSDN博客 VUE2 学习笔记3 v-on、事件修饰符、键盘事件_vue2组件 点击事件-CSDN博客 VU2 学习笔记4 计算属性、监视属性-CSDN博客 VUE2 学习…

【motion】HumanML3D 的安装1:环境搭建

https://github.com/EricGuo5513/HumanML3D/issues/10 (base) root@k8s-master-pfsrv:/home/zhangbin/perfwork/01_ai/15_HumanML3D# conda env create -f environment.yaml Retrieving notices: ...working... done Channels:- defaults Platform: linux-64 Collecting