Linux:基于socket套接字写的简易英译汉翻译服务器

一、项目背景

在上一篇博客中,我们掌握了 UDP Socket 编程的核心基础和接口,本次将基于这些知识实现第一个实战项目 ------简易英译汉翻译服务器

该项目采用C/S 架构(客户端 / 服务端),基于 UDP 协议实现,核心功能是:客户端输入英文单词,发送到服务端,服务端查询字典并返回对应的中文释义,若单词不存在则返回 "Unknown / 未查到"

二、项目整体设计

1. 架构设计

  • 服务端:负责加载字典文件到内存、监听固定端口、接收客户端的单词请求、查询字典并返回释义
  • 客户端:负责获取用户输入的英文单词、发送到服务端、接收服务端返回的中文释义并打印
  • 通信协议:UDP(无连接,无需维护客户端状态,服务端可同时响应多个客户端请求)

2. 核心功能模块

服务端模块
  1. Socket 通信模块:基于 UDP Socket 实现,包含创建、绑定、接收、发送接口
  2. 字典加载模块:从本地字典文件(txt)读取英文单词 - 中文释义映射,存储到哈希表(unordered_map),提高查询效率
  3. 业务处理模块 :通过回调函数实现,接收客户端的单词请求,查询哈希表并生成响应结果
  4. 主逻辑模块:初始化 Socket、加载字典、进入循环接收请求,调用业务处理模块并返回响应
客户端模块
  1. Socket 通信模块:基于 UDP Socket 实现,创建 Socket、发送单词请求、接收服务端响应
  2. 用户交互模块:获取用户从标准输入的英文单词,打印服务端返回的中文释义
  3. 主逻辑模块:初始化 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)。

核心步骤:

  1. 打开字典文件(ifstream)
  2. 逐行读取文件内容,通过分隔符: 分割为单词和释义
  3. 将键值对插入到 unordered_map 中
  4. 提供查询接口,根据单词返回释义,不存在则返回 "未查到"

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 → 未查到

六、项目扩展与优化

  1. 支持多字典:修改 Dict 类,支持加载多个字典文件,或加载中英 / 英中双向字典
  2. 支持模糊查询:当单词不存在时,返回相似的英文单词,提高用户体验
  3. 增加日志功能:将服务端的日志写入文件,而非仅打印到终端,方便后续排查问题
  4. 异常处理优化:增加对字典文件空、单词过长、网络中断等异常情况的处理
  5. 支持 TCP 协议:将 UDP 替换为 TCP,实现可靠的查词服务,适用于对数据可靠性要求高的场景
  6. 客户端优化:增加命令行参数解析、输入提示、历史记录等功能

七、总结

本次项目基于 UDP Socket 编程实现了简易英译汉翻译服务器,核心掌握了:

  1. UDP Socket C/S 架构的实际开发流程
  2. 类的封装思想,将字典和网络通信封装为独立的类,提高代码复用性
  3. 回调函数的使用,实现网络层与业务层的解耦,让 UdpServer 类更通用
  4. 哈希表的实际应用,利用 unordered_map 实现高效的键值对查询
  5. Linux 下文件操作、命令行参数解析、网络字节序转换的综合使用

下一篇博客将实现第二个 UDP Socket 实战项目 ------简单 UDP 双向通信程序,在本次项目的基础上,实现客户端与服务端的双向实时通信,进一步掌握 UDP 的全双工特性

老样子,我把完整代码放在最后面,要是大家觉得麻烦,也可以直接访问我的gitee(在主页)哦~~

相关推荐
jianqiang.xue2 小时前
ESP32-P4 看门狗复位全解析:HP_SYS_HP_WDT_RESET 故障排查实战
单片机·mcu·esp32·idf
somi72 小时前
51单片机-04-DS18B20 数字温度传感器
单片机·嵌入式硬件·51单片机
至为芯2 小时前
PY32F003至为芯支持32位ARM内核的低成本MCU微控制器
单片机·集成电路·芯片
zjxtxdy2 小时前
STM32开发板简介
stm32·单片机·嵌入式硬件
【 STM32开发 】2 小时前
【STM32 + CubeMX 教程】RTC 实时时钟 之 闹钟 -- F407篇
stm32·单片机·嵌入式硬件
weiyvyy2 小时前
接口开发的完整流程:从需求到验证
驱动开发·嵌入式硬件·硬件架构·硬件工程
MC_J2 小时前
STM32+FMC驱动W9825G6 SDRAM程序以及遇到的问题讲解
stm32·单片机
少年潜行2 小时前
【开源】STM32驱动BH1750(附开源代码)
单片机
0南城逆流03 小时前
【STM32】知识点介绍八:UART/USART串口功能
stm32·单片机·嵌入式硬件