跳到主要内容Linux Socket 编程实战:基于 UDP 的简易英译汉翻译服务器 | 极客日志C++算法
Linux Socket 编程实战:基于 UDP 的简易英译汉翻译服务器
综述由AI生成实现了一个基于 Linux UDP Socket 的英译汉翻译服务器。采用 C/S 架构,服务端加载字典至哈希表以提高查询效率,利用回调函数解耦网络通信与业务逻辑。客户端发送单词请求,服务端返回释义。项目涵盖 Socket 创建、绑定、收发及文件操作,展示了 C++ 类封装与 STL 容器在实际网络编程中的应用。
魔法巫师22 浏览 项目背景
在掌握了 UDP Socket 编程的基础接口后,我们直接上手一个实战项目——简易英译汉翻译服务器。这个项目采用经典的 C/S 架构,基于 UDP 协议实现。核心逻辑很简单:客户端输入英文单词并发送,服务端查询内存中的字典文件,返回对应的中文释义;如果找不到,则提示未查到。
整体设计思路
1. 架构与通信
- 服务端:加载字典到内存,监听固定端口,接收请求,查表并回复。
- 客户端:获取用户输入,发送请求,打印结果。
- 协议:UDP。无连接特性意味着不需要维护复杂的会话状态,服务端可以并发响应多个客户端的请求。
2. 核心模块划分
为了代码的可维护性,我们将功能拆分为几个独立模块:
- Socket 通信:封装底层的 socket、bind、recvfrom、sendto 操作。
- 字典管理:读取 txt 文件,解析键值对,存入哈希表(unordered_map)以便 O(1) 时间复杂度查询。
- 业务处理:通过回调函数解耦网络层和业务层。UdpServer 类只负责收发包,具体的查词逻辑由外部传入的函数决定。
3. 数据格式
字典文件使用纯文本格式,每行一个单词映射,例如 apple: 苹果。冒号加空格作为分隔符,方便后续按字符串分割处理。
关键技术点
哈希表加速查询
服务端启动时会一次性将字典文件读入内存。使用 C++ STL 的 unordered_map 存储 key-value 对,相比线性查找,查询效率有质的提升。对于高频查询场景,这是必要的优化手段。
类的封装与解耦
项目中定义了两个主要类:
- Dict 类:专门负责字典的加载和查询,对外暴露
Translate() 接口。
- UdpServer 类:封装 UDP 服务端的生命周期(创建、绑定、循环监听)。它不关心具体业务,而是通过
std::function<void(const string&, string*)> 类型的回调函数来执行业务逻辑。这种设计让 UdpServer 变得通用,未来只需更换回调函数即可适配不同的业务需求。
完整代码实现
1. 字典文件 (dict.txt)
确保与服务端程序在同一目录下,内容示例如下:
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
happy: 快乐的
sad: 悲伤的
hello: 你好
world: 世界
Unknown: 未查到
2. 服务端代码 (dict_server.cpp)
这里整合了 Dict 类和 UdpServer 类。注意 HandleRequest 函数被注册为回调,静态对象 static Dict dict 保证了字典只在第一次调用时加载一次。
#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;
}
};
class UdpServer {
private:
int _sockfd;
uint16_t _port;
function<void(const string&, string*)> _func;
bool Init() {
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0) {
cerr << "创建 Socket 失败:" << strerror(errno) << endl;
return false;
}
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;
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) {}
~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;
}
}
}
};
void HandleRequest(const string& req, string* resp) {
static Dict dict;
*resp = dict.Translate(req);
}
int main(int argc, char* argv[]) {
uint16_t port = DEFAULT_PORT;
if (argc == 2) {
port = atoi(argv[1]);
}
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[]) {
if (argc != 3) {
Usage(argv[0]);
return 1;
}
string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
cerr << "创建 Socket 失败:" << strerror(errno) << endl;
return 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);
if (word == "q" || word == "Q") {
cout << "客户端退出" << endl;
break;
}
if (word.empty()) continue;
ssize_t n = sendto(sockfd, word.c_str(), word.size(), 0, (struct sockaddr*)&server, sizeof(server));
if (n < 0) {
cerr << "发送失败:" << strerror(errno) << endl;
continue;
}
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));
}
close(sockfd);
return 0;
}
编译与运行
1. 编译
使用 g++ 配合 C++11 标准编译,因为用到了 lambda 和 function 等特性。
g++ -o dict_server dict_server.cpp -std=c++11
g++ -o dict_client dict_client.cpp -std=c++11
2. 启动服务端
./dict_server
./dict_server 9999
3. 启动客户端
本地测试指向 127.0.0.1,远程测试需替换为服务器公网 IP。如果是云服务器,记得在安全组放行 UDP 协议对应端口。
./dict_client 127.0.0.1 8888
4. 效果演示
>> apple
apple → 苹果
>> test
test → 未查到
>> q
客户端退出
扩展方向
- 多字典支持:允许加载多个词典文件。
- 模糊查询:当单词不存在时,推荐相似拼写。
- 日志持久化:将访问日志写入文件而非终端。
- TCP 支持:如果对可靠性要求高,可切换为 TCP 协议。
- 异常处理:增强对空文件、超长输入等边界情况的容错。
总结
本次实战通过 UDP Socket 实现了完整的英译汉服务,涵盖了 Linux 网络编程的核心流程。重点实践了类的封装思想,利用 unordered_map 提升了查询性能,并通过回调函数实现了网络层与业务层的解耦。这些模式在实际开发中非常常见,掌握它们有助于构建更健壮的网络应用。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online