当前位置: 首页 > news >正文

Linux网络编程【基于UDP网络通信的字典翻译服务】

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.dtcms.com/a/311155.html

相关文章:

  • M|银翼杀手
  • Web 开发 10
  • K8s+Nginx-ingress+Websocket基础知识理解
  • 系统思考:超越线性分析
  • python创建一个excel文件
  • MyBatis 批量操作 XML 实现方式
  • 【BTC】挖矿难度调整
  • Vue 详情模块 3
  • Matplotlib - Python图表可视化利器
  • Vue3核心语法进阶(computed与监听)
  • 除数博弈(动态规划)
  • cs336之注意pytorch的tensor在哪里?(assert的使用)
  • vue3渲染html数据并实现文本修改
  • 【7.5 Unity AssetPostprocessor】
  • 大模型 + 垂直场景:搜索 / 推荐 / 营销 / 客服领域开发有哪些新玩法?
  • Flask 框架全面详解
  • C语言字符函数和字符串函数全解析:从使用到模拟实现
  • MyBatis与MySQL
  • 【安装教程】Docker Desktop 安装与使用教程
  • 从毫秒到真义:构建工业级RAG系统的向量检索优化指南
  • Python爬虫实战:研究mahotas库,构建图像获取及处理系统
  • (思维)洛谷 P13551 ももいろの鍵 题解
  • 位菜:仪式锚与价值符
  • 24黑马SpringCloud的Docker本地目录挂载出现相关问题解决
  • 【图像处理基石】用Python实现基础滤镜效果
  • LLM中Function Call的原理及应用
  • 工业仪表盘识别误检率↓79%!陌讯多模态融合算法在设备巡检中的落地优化​
  • 安全光幕Muting功能程序逻辑设计
  • [mssql] 分析SQL Server中执行效率较低的SQL语句
  • Git、Gitee、GitHub、GitLab完整讲解:从基础到进阶