一、项目背景
本项目旨在基于 UDP Socket 编程实现一个简易英译汉翻译服务器。
该项目采用 C/S 架构(客户端/服务端),基于 UDP 协议实现,核心功能是:客户端输入英文单词,发送到服务端,服务端查询字典并返回对应的中文释义,若单词不存在则返回'Unknown / 未查到'。
二、项目整体设计
1. 架构设计
- 服务端:负责加载字典文件到内存、监听固定端口、接收客户端的单词请求、查询字典并返回释义
- 客户端:负责获取用户输入的英文单词、发送到服务端、接收服务端返回的中文释义并打印
- 通信协议:UDP(无连接,无需维护客户端状态,服务端可同时响应多个客户端请求)
2. 核心功能模块
服务端模块
- Socket 通信模块:基于 UDP Socket 实现,包含创建、绑定、接收、发送接口
- 字典加载模块:从本地字典文件(txt)读取英文单词 - 中文释义映射,存储到哈希表(unordered_map),提高查询效率
- 业务处理模块:通过回调函数实现,接收客户端的单词请求,查询哈希表并生成响应结果
- 主逻辑模块:初始化 Socket、加载字典、进入循环接收请求,调用业务处理模块并返回响应
客户端模块
- Socket 通信模块:基于 UDP Socket 实现,创建 Socket、发送单词请求、接收服务端响应
- 用户交互模块:获取用户从标准输入的英文单词,打印服务端返回的中文释义
- 主逻辑模块:初始化 Socket、循环处理用户输入、与服务端通信
3. 字典文件设计
字典文件采用 txt 格式,每行一个键值对,格式为英文单词:中文释义(冒号后加空格,作为分隔符),示例(dict.txt):
apple: 苹果 banana: 香蕉 cat: 猫 dog: 狗 book: 书 happy: 快乐的 sad: 悲伤的 hello: 你好 world: 世界 Unknown: 未查到
三、核心技术点解析
1. 字典加载与查询 —— 哈希表(unordered_map)
为了提高单词查询效率,服务端将字典文件的内容加载到 C++ STL 的 unordered_map 中,key 为英文单词(string),value 为中文释义(string),查询时间复杂度为 O(1)。
核心步骤:
- 打开字典文件(ifstream)
- 逐行读取文件内容,通过分隔符
:分割为单词和释义 - 将键值对插入到 unordered_map 中
- 提供查询接口,根据单词返回释义,不存在则返回'未查到'
2. 代码封装思想 —— 类的封装
将重复的功能封装为类,提高代码的复用性和可维护性,本次项目主要封装两个类:
- Dict 类:封装字典的加载和查询功能,对外提供 Translate() 接口
- UdpServer 类:封装 UDP Socket 服务端的核心接口(创建、绑定、启动),通过回调函数解耦'网络通信'和'业务处理'
3. 回调函数解耦 —— function<void (const string&, string*)>
服务端的核心是'网络通信'和'业务处理',为了让 UdpServer 类更通用(可适配不同业务),采用 C++11 的 function 回调函数将业务处理逻辑传递给 UdpServer 类,实现网络层与业务层的解耦。
即 UdpServer 类只负责网络通信(接收请求、发送响应),不关心具体的业务处理,业务处理由外部的回调函数实现(本次为字典查询)。
四、完整代码实现(C++)
1. 字典文件(dict.txt)
创建上述格式的 dict.txt,与服务端可执行文件放在同一目录下。
2. 服务端代码(dict_server.cpp)
整合 Dict 类和 UdpServer 类,实现核心功能:
#include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fstream> #include <unordered_map> #include <functional> #include <cstdlib> #include <cerrno> using namespace std; // 全局常量 const uint16_t DEFAULT_PORT = 8888; const int DEFAULT_BUFF_SIZE = 1024; const string DICT_PATH = "./dict.txt"; const string SEP = ": "; const string UNKNOWN = "未查到"; // 字典类:封装加载和查询功能 class Dict { private: unordered_map<string, string> _dict; // 存储单词 - 释义映射 // 加载字典文件到哈希表 void LoadDict() { ifstream in(DICT_PATH); if (!in.is_open()) { cerr << "打开字典文件失败:" << strerror(errno) << endl; return; } string line; while (getline(in, line)) { if (line.empty()) continue; size_t pos = line.find(SEP); if (pos == string::npos) continue; // 格式错误,跳过 string key = line.substr(0, pos); string value = line.substr(pos + SEP.size()); _dict.insert(make_pair(key, value)); } in.close(); cout << "字典加载完成,共加载" << _dict.size() << "个单词" << endl; } public: // 构造函数:初始化时加载字典 Dict() { LoadDict(); } // 查询接口:根据单词返回释义 string Translate(const string& key) { auto iter = _dict.find(key); if (iter == _dict.end()) { return UNKNOWN; } return iter->second; } }; // UDP 服务端类:封装网络通信功能,解耦业务处理 class UdpServer { private: int _sockfd; // Socket 描述符 uint16_t _port; // 监听端口 function<void(const string&, string*)> _func; // 业务处理回调函数 // 初始化 Socket:创建 + 绑定 bool Init() { // 1. 创建 UDP Socket _sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd < 0) { cerr << "创建 Socket 失败:" << strerror(errno) << endl; return false; } // 2. 绑定 IP 和端口 struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = INADDR_ANY; // 绑定本机所有 IP if (bind(_sockfd, (struct sockaddr*)&local, sizeof(local)) < 0) { cerr << "绑定端口失败:" << strerror(errno) << endl; close(_sockfd); return false; } cout << "UDP 服务端初始化成功,监听端口:" << _port << endl; return true; } public: // 构造函数:传入端口和业务处理回调函数 UdpServer(uint16_t port = DEFAULT_PORT, function<void(const string&, string*)> func = nullptr) : _port(port), _func(func), _sockfd(-1) {} // 析构函数:关闭 Socket ~UdpServer() { if (_sockfd >= 0) { close(_sockfd); } } // 启动服务端:循环接收请求,调用回调函数处理,返回响应 void Start() { if (!Init() || _func == nullptr) { cerr << "服务端启动失败" << endl; return; } char buffer[DEFAULT_BUFF_SIZE] = {0}; while (true) { // 无限循环,持续提供服务 struct sockaddr_in peer; socklen_t peer_len = sizeof(peer); // 接收客户端请求 ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &peer_len); if (n > 0) { buffer[n] = 0; string req = buffer; string resp; // 调用回调函数处理业务(查询字典) _func(req, &resp); // 向客户端返回响应 sendto(_sockfd, resp.c_str(), resp.size(), 0, (struct sockaddr*)&peer, peer_len); // 打印日志 string peer_ip = inet_ntoa(peer.sin_addr); uint16_t peer_port = ntohs(peer.sin_port); cout << "[" << peer_ip << ":" << peer_port << "] 查词:" << req << " → " << resp << endl; } } } }; // 业务处理函数:调用 Dict 的 Translate 接口 void HandleRequest(const string& req, string* resp) { static Dict dict; // 静态对象,只加载一次字典 *resp = dict.Translate(req); } // 主函数 int main(int argc, char* argv[]) { // 可选:通过命令行指定端口,如./dict_server 9999 uint16_t port = DEFAULT_PORT; if (argc == 2) { port = atoi(argv[1]); } // 创建 UDP 服务端,传入端口和业务处理回调函数 UdpServer server(port, HandleRequest); // 启动服务端 server.Start(); return 0; }
3. 客户端代码(dict_client.cpp)
实现用户交互和与服务端的通信:
#include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <cerrno> #include <cstdlib> using namespace std; // 全局常量 const int DEFAULT_BUFF_SIZE = 1024; // 用法提示 void Usage(const string& proc) { cout << "Usage: " << proc << " server_ip server_port" << endl; cout << "Example: " << proc << " 127.0.0.1 8888" << endl; } // 客户端主逻辑 int main(int argc, char* argv[]) { // 校验命令行参数:./dict_client server_ip server_port if (argc != 3) { Usage(argv[0]); return 1; } string server_ip = argv[1]; uint16_t server_port = atoi(argv[2]); // 1. 创建 UDP Socket int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { cerr << "创建 Socket 失败:" << strerror(errno) << endl; return 2; } // 2. 填充服务端地址结构 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(server_port); server.sin_addr.s_addr = inet_addr(server_ip.c_str()); if (server.sin_addr.s_addr == INADDR_NONE) { cerr << "IP 地址格式错误" << endl; close(sockfd); return 3; } cout << "客户端启动成功,连接服务端:" << server_ip << ":" << server_port << endl; cout << "请输入英文单词(输入 q 退出):" << endl; string word; char buffer[DEFAULT_BUFF_SIZE] = {0}; while (true) { cout << ">> "; getline(cin, word); // 输入 q 退出 if (word == "q" || word == "Q") { cout << "客户端退出" << endl; break; } if (word.empty()) { continue; } // 3. 发送单词请求到服务端 ssize_t n = sendto(sockfd, word.c_str(), word.size(), 0, (struct sockaddr*)&server, sizeof(server)); if (n < 0) { cerr << "发送失败:" << strerror(errno) << endl; continue; } // 4. 接收服务端的中文释义 struct sockaddr_in temp; socklen_t temp_len = sizeof(temp); ssize_t m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &temp_len); if (m > 0) { buffer[m] = 0; cout << word << " → " << buffer << endl; } else { cerr << "接收失败:" << strerror(errno) << endl; } // 清空缓冲区 memset(buffer, 0, sizeof(buffer)); } // 5. 关闭 Socket close(sockfd); return 0; }
五、编译与运行
1. 编译代码
# 编译服务端 g++ -o dict_server dict_server.cpp -std=c++11 # 编译客户端 g++ -o dict_client dict_client.cpp -std=c++11
2. 启动服务端
# 方式 1:使用默认端口 8888 ./dict_server # 方式 2:指定端口,如 9999 ./dict_server 9999
服务端启动成功会输出:字典加载完成,共加载 N 个单词 + UDP 服务端初始化成功,监听端口:8888。
3. 启动客户端
本地测试(服务端和客户端在同一台机器):
./dict_client 127.0.0.1 8888
远程测试(服务端在远程服务器上,客户端在本地):
./dict_client 服务器公网 IP 8888
注意:服务器需要在防火墙中放行对应的端口(如 8888),协议为 UDP。
4. 运行效果
客户端:
客户端启动成功,连接服务端:127.0.0.1:8888 请输入英文单词(输入 q 退出): >> apple apple → 苹果 >> happy happy → 快乐的 >> test test → 未查到 >> q 客户端退出
服务端:
字典加载完成,共加载 10 个单词 UDP 服务端初始化成功,监听端口:8888 [127.0.0.1:40652] 查词:apple → 苹果 [127.0.0.1:40652] 查词:happy → 快乐的 [127.0.0.1:40652] 查词:test → 未查到
六、项目扩展与优化
- 支持多字典:修改 Dict 类,支持加载多个字典文件,或加载中英/英中双向字典
- 支持模糊查询:当单词不存在时,返回相似的英文单词,提高用户体验
- 增加日志功能:将服务端的日志写入文件,而非仅打印到终端,方便后续排查问题
- 异常处理优化:增加对字典文件空、单词过长、网络中断等异常情况的处理
- 支持 TCP 协议:将 UDP 替换为 TCP,实现可靠的查词服务,适用于对数据可靠性要求高的场景
- 客户端优化:增加命令行参数解析、输入提示、历史记录等功能
七、总结
本次项目基于 UDP Socket 编程实现了简易英译汉翻译服务器,核心掌握了:
- UDP Socket C/S 架构的实际开发流程
- 类的封装思想,将字典和网络通信封装为独立的类,提高代码复用性
- 回调函数的使用,实现网络层与业务层的解耦,让 UdpServer 类更通用
- 哈希表的实际应用,利用 unordered_map 实现高效的键值对查询
- Linux 下文件操作、命令行参数解析、网络字节序转换的综合使用
