《从 0 到 1 搭建网络翻译器:UDP 协议 + 哈希表实战,新手也能看懂(附完整源码)》_哈希翻译器
概述
实现了一个基于 UDP 协议的简单网络翻译器,采用客户端 - 服务器架构,支持通过网络进行英文单词到中文的翻译功能。系统使用 C++ 语言开发,主要利用 Linux 系统下的 socket 编程接口实现网络通信,并通过哈希表实现高效的单词查询。
组成模块
网络通信模块(UdpServer/UdpClient)
基于 UDP 协议实现客户端与服务器的网络通信
服务器端支持绑定指定端口,持续监听并处理客户端请求
客户端支持指定服务器 IP 和端口,发送查询请求并接收结果
字典模块(Dict)
从指定文本文件加载单词及其中文解释
使用哈希表(unordered_map)存储单词映射,提供 O (1) 时间复杂度的查询
支持单词翻译功能,未找到时返回 “None”
日志模块(Log)
支持控制台和文件两种日志输出策略
日志包含时间戳、日志等级、进程 ID、源文件信息等
记录系统运行状态、客户端请求及处理结果
工具类
互斥锁模块(Mutex):保证多线程环境下的线程安全
网络地址模块(InetAddr):处理 IP 地址和端口号的转换
源码
Mutex.hpp
#pragma once#include #include /** * @brief 互斥锁模块,提供线程同步机制 * 包含基础互斥锁类和基于RAII的自动锁类,用于多线程环境下保护共享资源 */namespace MutexModule{ /** * @brief 互斥锁类,封装了pthread库的互斥锁操作 * 提供锁的创建、销毁、加锁和解锁功能 */ class Mutex { public: /** * @brief 构造函数,初始化互斥锁 * 使用默认属性创建互斥锁,成功初始化后可用于线程同步 */ Mutex() { // 初始化互斥锁,第二个参数为nullptr表示使用默认属性 pthread_mutex_init(&_mutex, nullptr); } /** * @brief 加锁操作 * 若锁未被占用,则当前线程获取锁并继续执行; * 若锁已被占用,则当前线程会阻塞等待直到获取到锁 */ void Lock() { // 调用pthread库的加锁函数 int n = pthread_mutex_lock(&_mutex); (void)n; // 忽略返回值,实际应用中可根据需要处理错误码 } /** * @brief 解锁操作 * 释放当前线程持有的锁,允许其他等待该锁的线程获取锁 * 注意:只有持有锁的线程才能调用此函数,否则会导致未定义行为 */ void Unlock() { // 调用pthread库的解锁函数 int n = pthread_mutex_unlock(&_mutex); (void)n; // 忽略返回值,实际应用中可根据需要处理错误码 } /** * @brief 析构函数,销毁互斥锁 * 释放互斥锁所占用的系统资源,确保资源正确回收 */ ~Mutex() { pthread_mutex_destroy(&_mutex); } /** * @brief 获取底层pthread互斥锁指针 * 用于需要直接操作原始互斥锁的场景(如与条件变量配合使用) * @return 指向pthread_mutex_t类型的指针 */ pthread_mutex_t *Get() { return &_mutex; } private: pthread_mutex_t _mutex; ///< 底层pthread互斥锁对象 }; /** * @brief 自动锁类,基于RAII(资源获取即初始化)机制 * 在对象构造时自动加锁,析构时自动解锁,确保锁的正确释放,避免死锁 */ class LockGuard { public: /** * @brief 构造函数,对指定互斥锁进行加锁 * @param mutex 要操作的互斥锁引用,必须是已初始化的Mutex对象 */ LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); // 构造时自动加锁 } /** * @brief 析构函数,对互斥锁进行解锁 * 当对象超出作用域时自动调用,确保锁被释放,无论代码是否正常执行 */ ~LockGuard() { _mutex.Unlock(); // 析构时自动解锁 } // 禁用拷贝构造和赋值运算符,防止锁的异常传递 LockGuard(const LockGuard&) = delete; LockGuard& operator=(const LockGuard&) = delete; private: Mutex &_mutex; ///< 引用的互斥锁对象,用于在析构时解锁 };}
Log.hpp
#ifndef __LOG_HPP__#define __LOG_HPP__#include #include #include #include // C++17 filesystem库,用于目录操作#include #include #include #include #include // 用于获取进程ID getpid()#include \"Mutex.hpp\" // 引入互斥锁模块,保证线程安全namespace LogModule{ using namespace MutexModule; // 引入互斥锁命名空间 /** * @brief 日志消息的分隔符 * 使用\\r\\n作为换行符,兼容不同操作系统 */ const std::string gsep = \"\\r\\n\"; /** * @brief 日志输出策略基类(策略模式) * 定义日志输出的接口,具体输出方式由子类实现 */ class LogStrategy { public: /** * @brief 纯虚函数,用于同步输出日志消息 * @param message 要输出的日志消息字符串 */ virtual void SyncLog(const std::string &message) = 0; /** * @brief 虚析构函数,确保子类析构函数能被正确调用 */ virtual ~LogStrategy() = default; }; /** * @brief 控制台日志输出策略 * 实现向控制台输出日志的功能,线程安全 */ class ConsoleLogStrategy : public LogStrategy { public: /** * @brief 构造函数,初始化控制台日志策略 */ ConsoleLogStrategy() = default; /** * @brief 向控制台输出日志消息 * 使用互斥锁保证多线程环境下的输出完整性 * @param message 要输出的日志消息 */ void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); // RAII自动加锁,离开作用域自动解锁 std::cout << message << gsep; // 输出日志消息并添加分隔符 } /** * @brief 析构函数 */ ~ConsoleLogStrategy() override = default; private: Mutex _mutex; ///< 互斥锁,保证控制台输出的线程安全 }; /** * @brief 文件日志输出策略 * 实现向文件输出日志的功能,支持自动创建目录,线程安全 */ const std::string defaultpath = \"./log\"; ///< 默认日志文件目录 const std::string defaultfile = \"my.log\"; ///< 默认日志文件名 class FileLogStrategy : public LogStrategy { public: /** * @brief 构造函数,初始化文件日志策略 * @param path 日志文件所在目录,默认为\"./log\" * @param file 日志文件名,默认为\"my.log\" */ FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); // 加锁保证线程安全 // 检查目录是否存在,不存在则创建 if (!std::filesystem::exists(_path)) { try { std::filesystem::create_directories(_path); // 创建多级目录 } catch (const std::filesystem::filesystem_error &e) { std::cerr << \"创建日志目录失败: \" << e.what() << \'\\n\'; } } } /** * @brief 向文件输出日志消息 * 使用追加模式打开文件,保证日志不被覆盖,线程安全 * @param message 要输出的日志消息 */ void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); // 加锁保证线程安全 // 拼接完整日志文件路径(处理目录末尾是否带\'/\'的情况) std::string filename = _path + (_path.back() == \'/\' ? \"\" : \"/\") + _file; // 以追加模式打开文件,不存在则创建 std::ofstream out(filename, std::ios::app); if (!out.is_open()) { std::cerr << \"打开日志文件失败: \" << filename << std::endl; return; } out << message << gsep; // 写入日志消息并添加分隔符 out.close(); // 关闭文件 } /** * @brief 析构函数 */ ~FileLogStrategy() override = default; private: std::string _path; ///< 日志文件所在路径 std::string _file; ///< 日志文件名 Mutex _mutex; ///< 互斥锁,保证文件操作的线程安全 }; /** * @brief 日志等级枚举 * 定义不同级别的日志,用于区分日志的重要程度 */ enum class LogLevel { DEBUG, ///< 调试信息,开发阶段使用 INFO, ///< 普通信息,记录正常运行状态 WARNING, ///< 警告信息,需要关注但不影响运行 ERROR, ///< 错误信息,功能异常但程序可继续运行 FATAL ///< 致命错误,程序可能无法继续运行 }; /** * @brief 将日志等级转换为字符串 * @param level 日志等级枚举值 * @return 对应的字符串表示 */ std::string Level2Str(LogLevel level) { switch (level) { case LogLevel::DEBUG: return \"DEBUG\"; case LogLevel::INFO: return \"INFO\"; case LogLevel::WARNING: return \"WARNING\"; case LogLevel::ERROR: return \"ERROR\"; case LogLevel::FATAL: return \"FATAL\"; default: return \"UNKNOWN\"; } } /** * @brief 获取当前时间戳字符串 * 格式为:年-月-日 时:分:秒 * @return 格式化的时间字符串 */ std::string GetTimeStamp() { time_t curr = time(nullptr); // 获取当前时间戳 struct tm curr_tm; localtime_r(&curr, &curr_tm); // 转换为本地时间(线程安全版本) char timebuffer[128]; // 格式化时间字符串 snprintf(timebuffer, sizeof(timebuffer), \"%4d-%02d-%02d %02d:%02d:%02d\", curr_tm.tm_year + 1900, // 年份需要加1900 curr_tm.tm_mon + 1, // 月份从0开始,需要加1 curr_tm.tm_mday, // 日 curr_tm.tm_hour, // 时 curr_tm.tm_min, // 分 curr_tm.tm_sec); // 秒 return timebuffer; } /** * @brief 日志器类 * 负责日志的构建、输出策略管理,是日志模块的核心类 */ class Logger { public: /** * @brief 构造函数,默认启用控制台日志输出 */ Logger() { EnableConsoleLogStrategy(); } /** * @brief 启用文件日志输出策略 * 切换日志输出到文件 */ void EnableFileLogStrategy() { _fflush_strategy = std::make_unique<FileLogStrategy>(); } /** * @brief 启用控制台日志输出策略 * 切换日志输出到控制台 */ void EnableConsoleLogStrategy() { _fflush_strategy = std::make_unique<ConsoleLogStrategy>(); } /** * @brief 日志消息类 * 用于构建单条日志消息,支持流式操作 */ class LogMessage { public: /** * @brief 构造函数,初始化日志消息的固定部分 * @param level 日志等级 * @param src_name 日志所在源文件名称 * @param line_number 日志所在行号 * @param logger 关联的日志器对象 */ LogMessage(LogLevel level, std::string src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), // 获取当前进程ID _src_name(std::move(src_name)), _line_number(line_number), _logger(logger) { // 构建日志的固定头部信息 std::stringstream ss; ss << \"[\" << _curr_time << \"] \" << \"[\" << Level2Str(_level) << \"] \" << \"[\" << _pid << \"] \" << \"[\" << _src_name << \"] \" << \"[\" << _line_number << \"] \" << \"- \"; _loginfo = ss.str(); } /** * @brief 流式操作符,用于添加日志内容 * 支持任意可输出到流的类型 * @param info 要添加的日志内容 * @return 自身引用,支持链式调用 */ template <typename T> LogMessage &operator<<(const T &info) { std::stringstream ss; ss << info; // 将数据转换为字符串 _loginfo += ss.str(); // 拼接日志内容 return *this; } /** * @brief 析构函数,自动输出日志消息 * 当日志消息对象销毁时,自动将完整日志输出 */ ~LogMessage() { // 如果日志策略存在,则输出日志 if (_logger._fflush_strategy) { _logger._fflush_strategy->SyncLog(_loginfo); } } private: std::string _curr_time; ///< 日志产生的时间戳 LogLevel _level; ///< 日志等级 pid_t _pid; ///< 进程ID std::string _src_name; ///< 源文件名称 int _line_number; ///< 行号 std::string _loginfo; ///< 完整的日志消息字符串 Logger &_logger; ///< 关联的日志器 }; /** * @brief 创建日志消息对象 * 用于启动一条日志的构建 * @param level 日志等级 * @param name 源文件名称 * @param line 行号 * @return 日志消息对象 */ LogMessage operator()(LogLevel level, std::string name, int line) { return LogMessage(level, std::move(name), line, *this); } /** * @brief 析构函数 */ ~Logger() = default; private: std::unique_ptr<LogStrategy> _fflush_strategy; ///< 日志输出策略,智能指针管理 }; // 全局日志对象,供外部直接使用 Logger logger; /** * @brief 日志宏定义,简化日志使用 * 自动获取当前文件名称和行号 * @param level 日志等级 */ #define LOG(level) logger(level, __FILE__, __LINE__) /** * @brief 启用控制台日志的宏定义 */ #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() /** * @brief 启用文件日志的宏定义 */ #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()}#endif
InetAddr.hpp
#pragma once#include #include #include #include #include // 提供inet_ntoa等IP地址转换函数#include // 提供sockaddr_in等网络地址结构体定义/** * @brief 网络地址封装类 * 用于处理网络地址(IP和端口)的转换和存储, * 提供主机字节序与网络字节序之间的转换功能 */class InetAddr{public: /** * @brief 构造函数,通过sockaddr_in结构体初始化 * 自动将网络字节序的IP和端口转换为主机字节序并存储 * @param addr 网络地址结构体引用,包含网络字节序的IP和端口 */ InetAddr(struct sockaddr_in &addr) : _addr(addr) { // 将网络字节序的端口号转换为主机字节序并存储 _port = ntohs(_addr.sin_port); // 将网络字节序的32位IP地址转换为点分十进制字符串格式 _ip = inet_ntoa(_addr.sin_addr); } /** * @brief 获取主机字节序的端口号 * @return 以uint16_t类型返回端口号 */ uint16_t Port() { return _port; } /** * @brief 获取点分十进制格式的IP地址字符串 * @return 以std::string类型返回IP地址 */ std::string Ip() { return _ip; } /** * @brief 析构函数 * 无需额外资源释放,默认即可 */ ~InetAddr() {}private: struct sockaddr_in _addr; ///< 存储原始的网络地址结构体(网络字节序) std::string _ip; ///< 点分十进制格式的IP地址字符串(主机字节序表示) uint16_t _port; ///< 主机字节序表示的端口号};
Dictionary.txt (字典文件)
apple: 苹果banana: 香蕉cat: 猫dog: 狗book: 书pen: 笔happy: 快乐的sad: 悲伤的hello: : 你好run: 跑jump: 跳teacher: 老师student: 学生car: 汽车bus: 公交车love: 爱hate: 恨hello: 你好goodbye: 再见summer: 夏天winter: 冬天
Dict.hpp
#pragma once#include #include #include #include #include \"Log.hpp\"#include \"InetAddr.hpp\"/** * @brief 默认字典文件路径 * 当未指定字典文件时使用此默认路径 */const std::string defaultdict = \"./dictionary.txt\";/** * @brief 字典文件中的分隔符 * 用于分隔英文单词和其中文解释(格式:\"单词: 解释\") */const std::string sep = \": \";using namespace LogModule; // 引入日志模块命名空间/** * @brief 字典翻译类 * 负责从字典文件加载数据并提供单词翻译功能,支持日志记录 */class Dict{public: /** * @brief 构造函数 * @param path 字典文件的路径,默认为defaultdict */ Dict(const std::string &path = defaultdict) : _dict_path(path) { } /** * @brief 加载字典文件到内存 * 解析字典文件内容并存储到哈希表中,支持日志记录加载过程 * @return 加载成功返回true,失败返回false */ bool LoadDict() { // 打开字典文件 std::ifstream in(_dict_path); if (!in.is_open()) { LOG(LogLevel::DEBUG) << \"打开字典: \" << _dict_path << \" 错误\"; return false; } std::string line; // 逐行读取字典文件 while (std::getline(in, line)) { // 查找分隔符位置(格式示例:\"apple: 苹果\") auto pos = line.find(sep); if (pos == std::string::npos) { LOG(LogLevel::WARNING) << \"解析: \" << line << \" 失败(未找到分隔符)\"; continue; } // 提取英文单词和中文解释 std::string english = line.substr(0, pos); std::string chinese = line.substr(pos + sep.size()); // 检查提取结果是否有效 if (english.empty() || chinese.empty()) { LOG(LogLevel::WARNING) << \"没有有效内容: \" << line; continue; } // 将单词和解释存入哈希表 _dict.insert(std::make_pair(english, chinese)); LOG(LogLevel::DEBUG) << \"加载: \" << line; } in.close(); // 关闭文件 return true; } /** * @brief 翻译单词并记录日志 * 在字典中查找单词并返回其中文解释,同时记录客户端信息和翻译结果 * @param word 要翻译的英文单词 * @param client 客户端网络地址对象,用于日志记录 * @return 单词的中文解释,未找到则返回\"None\" */ std::string Translate(const std::string &word, InetAddr &client) { // 在哈希表中查找单词 auto iter = _dict.find(word); if (iter == _dict.end()) { // 记录未找到单词的日志 LOG(LogLevel::DEBUG) << \"进入到了翻译模块, [\" << client.Ip() << \" : \" << client.Port() << \"]# \" << word << \"->None\"; return \"None\"; } // 记录翻译成功的日志 LOG(LogLevel::DEBUG) << \"进入到了翻译模块, [\" << client.Ip() << \" : \" << client.Port() << \"]# \" << word << \"->\" << iter->second; return iter->second; } /** * @brief 析构函数 * 无需额外资源释放,默认实现即可 */ ~Dict() { }private: std::string _dict_path; ///< 字典文件的路径(包含文件名) /** * @brief 存储单词与解释的哈希表 * key: 英文单词,value: 中文解释,支持O(1)时间复杂度的查找 */ std::unordered_map<std::string, std::string> _dict;};
UdpServer.hpp
#pragma once#include #include #include #include #include #include #include #include #include \"Log.hpp\"#include \"InetAddr.hpp\"using namespace LogModule;/** * @brief 定义服务器处理函数的类型 * 输入参数: 客户端发送的字符串数据, 客户端的网络地址对象 * 返回值: 服务器处理后要返回给客户端的字符串 */using func_t = std::function<std::string(const std::string&, InetAddr&)>;/** * @brief 套接字描述符的默认初始值 */const int defaultfd = -1;/** * @brief UDP服务器类, 用于创建和管理UDP网络通信服务 */class UdpServer{public: /** * @brief 构造函数 * @param port 服务器要绑定的端口号 * @param func 处理客户端请求的回调函数 */ UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), // 初始化套接字描述符为默认值 _port(port), // 保存服务器端口号 _isrunning(false), // 服务器初始为未运行状态 _func(func) // 保存回调函数 { } /** * @brief 初始化服务器 * 负责创建套接字和绑定网络地址 */ void Init() { // 1. 创建UDP套接字 // AF_INET: 使用IPv4地址族 // SOCK_DGRAM: 使用数据报套接字(UDP) // 0: 自动选择合适的协议 _sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd < 0) { LOG(LogLevel::FATAL) << \"创建套接字失败!\"; exit(1); } LOG(LogLevel::INFO) << \"创建套接字成功, 套接字描述符: \" << _sockfd; // 2. 绑定套接字到指定的端口 // 2.1 初始化本地地址结构体 struct sockaddr_in local; bzero(&local, sizeof(local)); // 清空结构体 local.sin_family = AF_INET; // 设置地址族为IPv4 // 将端口号从主机字节序转换为网络字节序 local.sin_port = htons(_port); // INADDR_ANY表示绑定到本机所有可用网络接口 // 对于服务器, 通常不需要指定具体IP, 这样可以接收来自任何网络接口的请求 local.sin_addr.s_addr = INADDR_ANY; // 2.2 执行绑定操作 // 将套接字与本地地址绑定 int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); if (n < 0) { LOG(LogLevel::FATAL) << \"绑定套接字失败\"; exit(2); } LOG(LogLevel::INFO) << \"绑定套接字成功, 套接字描述符: \" << _sockfd; } /** * @brief 启动服务器 * 进入循环, 持续接收客户端消息并处理后返回响应 */ void Start() { _isrunning = true; // 标记服务器为运行状态 // 服务器主循环 while (_isrunning) { char buffer[1024]; // 用于存储接收的数据 struct sockaddr_in peer; // 用于存储客户端地址信息 socklen_t len = sizeof(peer); // 客户端地址结构体的长度 // 1. 接收客户端发送的数据 // 从套接字接收数据, 同时获取发送方(客户端)的地址信息 ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); if (s > 0) // 成功接收数据 { InetAddr client(peer); // 封装客户端地址信息 buffer[s] = 0; // 为字符串添加结束符 // 调用回调函数处理接收到的数据 std::string result = _func(buffer, client); // 2. 向客户端发送处理结果 sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len); } } } /** * @brief 析构函数 * 负责释放套接字资源 */ ~UdpServer() { // 关闭套接字 if (_sockfd != defaultfd) { close(_sockfd); } }private: int _sockfd; // 套接字描述符 uint16_t _port; // 服务器端口号 bool _isrunning; // 服务器运行状态标志 func_t _func; // 处理客户端请求的回调函数};
UdpServer.cc
#include #include #include \"Dict.hpp\" // 提供字典翻译功能#include \"UdpServer.hpp\" // 提供UDP网络通信功能/** * @brief 默认的消息处理函数(测试用) * 简单拼接字符串作为响应,仅用于功能测试 * @param message 客户端发送的消息 * @return 处理后的响应消息 */std::string defaulthandler(const std::string &message){ std::string hello = \"hello, \"; hello += message; return hello;}/** * @brief 程序功能说明 * 1. 实现一个翻译系统,接收英文单词并返回其中文解释 * 2. 字典数据从文件加载(默认路径:./dictionary.txt) * 3. 基于UDP协议进行网络通信 */// 程序入口:./udpserver portint main(int argc, char *argv[]){ // 检查命令行参数是否正确(仅需要端口号) if(argc != 2) { std::cerr << \"Usage: \" << argv[0] << \" port\" << std::endl; return 1; } // 解析端口号(字符串转无符号16位整数) uint16_t port = std::stoi(argv[1]); // 启用控制台日志输出(方便调试和查看运行状态) Enable_Console_Log_Strategy(); // 1. 初始化字典对象并加载字典文件 Dict dict; // 创建字典对象(使用默认字典文件路径) dict.LoadDict(); // 从文件加载单词数据到内存 // 2. 创建UDP服务器并设置处理逻辑 // 使用智能指针管理服务器对象生命周期 std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>( port, // 服务器绑定的端口号 [&dict](const std::string &word, InetAddr& cli) -> std::string { // lambda表达式作为回调函数:接收单词并调用字典翻译 return dict.Translate(word, cli); } ); // 初始化服务器(创建套接字、绑定地址等) usvr->Init(); // 启动服务器(进入消息循环,持续处理客户端请求) usvr->Start(); return 0;}
UdpClient.cc
#include #include #include #include #include #include #include /** * @brief UDP客户端程序 * 功能:与UDP服务器建立通信,发送英文单词并接收其中文翻译结果 * 使用方式:./udpclient server_ip server_port */// 程序入口:需要传入服务器IP和端口号int main(int argc, char *argv[]){ // 检查命令行参数是否正确(需要服务器IP和端口号两个参数) if (argc != 3) { std::cerr << \"Usage: \" << argv[0] << \" server_ip server_port\" << std::endl; return 1; } // 解析命令行参数:服务器IP地址和端口号 std::string server_ip = argv[1]; uint16_t server_port = std::stoi(argv[2]); // 字符串转无符号16位整数 // 1. 创建UDP套接字 // AF_INET: 使用IPv4地址族 // SOCK_DGRAM: 使用数据报套接字(UDP协议) // 0: 自动选择合适的协议 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0) // 套接字创建失败 { std::cerr << \"socket error\" << std::endl; return 2; } // 2. 准备服务器地址信息 // 客户端通常不需要显式绑定本地地址和端口: // 首次发送数据时,操作系统会自动为客户端分配一个本地IP和随机端口 // 这样可以避免端口冲突,因为一个端口只能被一个进程绑定 struct sockaddr_in server; memset(&server, 0, sizeof(server)); // 清空结构体 server.sin_family = AF_INET; // 设置地址族为IPv4 server.sin_port = htons(server_port); // 将端口号从主机字节序转为网络字节序 // 将点分十进制IP字符串转为网络字节序的32位整数 server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // 3. 主循环:持续读取用户输入并与服务器通信 while(true) { // 读取用户输入的英文单词 std::string input; std::cout << \"Please Enter# \"; std::getline(std::cin, input); // 读取一行输入 // 发送数据到服务器 // sendto参数:套接字、数据、长度、标志、服务器地址、地址长度 int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server)); (void)n; // 忽略返回值,实际应用中可检查发送是否成功 // 接收服务器的响应 char buffer[1024]; // 存储接收数据的缓冲区 struct sockaddr_in peer; // 存储发送方(服务器)地址信息 socklen_t len = sizeof(peer); // 地址结构体长度 // recvfrom参数:套接字、缓冲区、长度、标志、发送方地址、地址长度指针 int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len); if(m > 0) // 成功接收数据 { buffer[m] = 0; // 添加字符串结束符 std::cout << \"翻译结果: \" << buffer << std::endl; // 输出翻译结果 } } // 实际不会执行到这里,因为上面是无限循环 // 关闭套接字(释放资源) close(sockfd); return 0;}
效果展示:
总结
采用 UDP 协议实现无连接的网络通信,适合简单查询场景
使用哈希表存储字典数据,提供高效查询性能
基于 RAII机制管理资源,如互斥锁自动加解锁
采用策略模式设计日志系统,方便扩展日志输出方式
封装网络地址处理,简化字节序转换操作
完善的错误处理和日志记录,便于调试和维护