【Linux网络】Socket编程实战,基于UDP协议的Dict Server

前言:

上文我们实现了对于基于UDP的EchoServer的Socket编程。【Linux网络】Socket编程实战,基于UDP协议的Echo Server-CSDN博客

我们再进一步优化更新其功能,使其实现一个翻译功能!


翻译模块

首先,我们通过上文的代码实现。我们发现若是想要实现单词翻译功能,我们只需要单词实现翻译模块功能。

客户端、服务端的创建套接字、bind、收发消息等等功能是不需要变的。我们只需要对客户端发来的信息做翻译处理即可!既在服务端中实现对翻译功能的调用。

TXT文件

首先,我们先创建一个.txt文本,用于规定翻译的文本范围

Dict.txt

apple: 苹果

banana: 香蕉

cat: 猫

dog: 狗

book: 书

pen: 笔

happy: 快乐的

sad: 悲伤的

run: 跑

jump: 跳

teacher: 老师

student: 学生

car: 汽车

bus: 公交车

love: 爱

hate: 恨

hello: 你好

goodbye: 再见

summer: 夏天

winter: 冬天

翻译模块实现

cpp 复制代码
//Dict.hpp

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
using namespace LogModule;

const std::string DefaultPath = "./Dict.txt";
const std::string step = ": ";

class Dict
{
public:
    Dict(const std::string &path = DefaultPath)
        : _path(path)
    {
    }

    void LoadDict()
    {
        // 打开对应文件
        std::ifstream inFile;
        inFile.open(_path, std::ios::in);
        // 如果没有打开成功
        if (!inFile) // 等价于:!inFile.is_open()
        {
            LOG(LogLevel::FATAL) << "文件打开失败!";
            return;
        }

        // 加载字典数据
        string line;
        // 读取文件中一整行内容(cin则只读取到空格)
        while (getline(inFile, line))
        {
            // 找到分隔符的位置
            auto pos = line.find(step);
            // 没有找到
            if (pos == std::string::npos)
            {
                LOG(LogLevel::DEBUG) << "单词解析失败!";
            }

            // 获取中英文
            std::string English = line.substr(0, pos);
            std::string Chinese = line.substr(pos + step.size());
            if (English.empty() || Chinese.empty())
            {
                LOG(LogLevel::DEBUG) << "单词信息残缺!";
            }

            // 插入map中
            _umap.insert(make_pair(English, Chinese));
            LOG(LogLevel::INFO) << "单词载入成功!";
        }

        // 关闭对应文件
        inFile.close();
    }

    string Translate(const std::string &word)
    {
        auto pos = _umap.find(word);
        if (pos == _umap.end())
        {
            return word + " -> None";
        }
        else
        {
            std::string ss = pos->second;
            return word + " -> " + ss;
        }
    }

private:
    std::string _path;
    unordered_map<std::string, std::string> _umap;
};

服务器调用翻译模块

先在UdpServer.cc中创建、初始化翻译模块对象,并传入对应方法。

cpp 复制代码
//UdpServer.cc

#include "UdpServer.hpp"
#include <cstdlib>
#include "Dict.hpp"

// 给出 端口号
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Please use: " << argv[0] << " PORT" << endl;
    }
    else
    {
        Dict dict;
        dict.LoadDict();
        uint16_t port = stoi(argv[1]); // 注:字符串转整数
        udpserver us(port, [&dict](std::string word)
                     { return dict.Translate(word); });
        us.Init();
        us.Start();
    }
}

随后UdpServer.hpp中直接调用该方法即可!

完整代码

客户端

cpp 复制代码
//UdpClient.cc

#include "Log.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace LogModule;

// 给出 ip地址 端口号
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Please use: " << argv[0] << " IP " << "PORT " << endl;
        exit(1);
    }

    uint32_t ip = inet_addr(argv[1]); // 注:字符串转合法ip地址
    uint16_t port = stoi(argv[2]);    // 注:字符串转整数

    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "创建套接字失败";
        exit(1);
    }
    LOG(LogLevel::INFO) << "创建套接字";

    // 绑定?不用显示绑定,OS会自动的绑定
    // 填写服务器信息
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = ip;
    local.sin_port = htons(port);

    // 一定是死循环
    while (true)
    {
        // 向服务器发送信息
        cout << "Please Cin # ";
        std::string buff;
        cin >> buff;
        // std::getline(std::cin, buff);
        // buff.size()-1 会丢失最后一个字符,应改为 buff.size()
        ssize_t s = sendto(sockfd, buff.c_str(), buff.size(), 0, (struct sockaddr *)&local, sizeof(local));
        if (s < 0)
        {
            LOG(LogLevel::WARNING) << "向服务器发送信息失败";
            exit(1);
        }

        // 接收服务器返回的信息
        char buffer[1024];
        memset(&buffer, 0, sizeof(buffer));
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        ssize_t ss = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
        if (ss < 0)
        {
            LOG(LogLevel::WARNING) << "接收服务器信息失败";
            exit(1);
        }
        printf("%s\n", buffer);
        memset(&buff, 0, sizeof(buffer)); // 清理缓存
    }
}

服务端

cpp 复制代码
//UdpServer.cc

#include "UdpServer.hpp"
#include <cstdlib>
#include "Dict.hpp"

// 给出 端口号
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Please use: " << argv[0] << " PORT" << endl;
    }
    else
    {
        Dict dict;
        dict.LoadDict();
        uint16_t port = stoi(argv[1]); // 注:字符串转整数
        udpserver us(port, [&dict](std::string word)
                     { return dict.Translate(word); });
        us.Init();
        us.Start();
    }
}
cpp 复制代码
//UdpServer.hpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

class udpserver
{
    using func_t = function<string(string)>;

public:
    udpserver(uint16_t port, func_t func)
        // : _addr(inet_addr(addr.c_str())), // 注:直接将其转换为合法的ip地址
        : _port(port),
          _func(func)
    {
        _running = false;
    }

    // 初始化:1.创建套接字 2.填充并绑定地址信息
    void Init()
    {
        // 1.创建套接字
        // 返回套接字描述符 地址族 数据类型 传输协议
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建套接字失败!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "创建套接字";

        // 2.绑定信息
        // 2.1填充信息
        struct sockaddr_in local;
        // 将指定内存块的所有字节清零
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET; // IPv4地址族
        // local.sin_addr.s_addr = _addr;   //IP地址(主机序列转化为网络序列)
        local.sin_addr.s_addr = INADDR_ANY; // 赋值为INADDR_ANY,表示任意地址
        local.sin_port = htons(_port);      // 端口号

        // 2.2绑定信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "绑定失败";
            exit(1);
        }
        LOG(LogLevel::INFO) << "绑定成功";
    }

    // 启动运行:一直运行不停止;1.接收客户端的信息 2.对客户端发送来的信息进行回显
    void Start()
    {
        // 一定是死循环
        _running = true;
        while (_running)
        {
            // 接收客户端的信息
            char buff[1024];
            struct sockaddr_in peer;
            unsigned int len = sizeof(peer);
            // 套接字描述符,数据存放的缓冲区,接收方式:默认,保存发送方的ip与端口,输入输出参数:输入peer的大小,输出实际读取的数据大小
            ssize_t s = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);

            // 显示发送方的ip与prot
            InetAddr iaddr(peer);
            cout << iaddr.ip() << " : " << iaddr.prot() << " : ";
            // 显示发送的信息
            buff[s] = 0;
            printf("%s\n", buff);

            // 回显消息
            if (s > 0)
            {
                // 调用自定义方法
                std::string ss = _func(string(buff));

                // 将数据发送给客户端
                // 套接字描述符,要发送的信息,发送方式:默认,接收方的ip与端口信息
                ssize_t t = sendto(_sockfd, ss.c_str(), ss.size(), 0, (struct sockaddr *)&peer, len);
                if (t < 0)
                {
                    LOG(LogLevel::WARNING) << "信息发送给客户端失败";
                }
            }
            memset(&buff, 0, sizeof(buff)); // 清理缓存
        }
    }

private:
    int _sockfd;
    uint32_t _addr;
    uint16_t _port;
    bool _running;

    // 回调方法
    func_t _func;
};
cpp 复制代码
//InetAddr.hpp

#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>

// 实现网络地址与主机地址的转换

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _prot = ntohl(_addr.sin_port);   // 网络地址转主机地址
        _ip = inet_ntoa(_addr.sin_addr); // 将4字节网络风格的IP -> 点分十进制的字符串风格的IP
    }

    uint16_t prot()
    {
        return _prot;
    }

    string ip()
    {
        return _ip;
    }

private:
    struct sockaddr_in _addr;
    uint16_t _prot;
    std::string _ip;
};
相关推荐
jenchoi4133 小时前
【2025-11-05】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·安全·web安全·网络安全
yunhuibin4 小时前
无锁化编程——c++内存序使用
c++
爱编程的鱼6 小时前
403 是什么意思?一文读懂 HTTP 状态码 403 及解决方法
网络·网络协议·http
zzzyyy5386 小时前
C++之vector容器
开发语言·c++
Unstoppable226 小时前
八股训练营第 8 天 | TCP连接三次握手的过程?TCP连接四次挥手的过程?HTTP的Keep-Alive是什么?
网络·tcp/ip·http·八股
_dindong6 小时前
Linux网络编程:应用层协议HTTP
网络·网络协议·http
Jerry2505097 小时前
什么是HTTPS?对网站有什么用?
网络·网络协议·http·网络安全·https·ssl
0和1的舞者7 小时前
网络的奥秘:HTTPS详解(八)
网络·网络协议·tcp/ip·http·https·四大件
uotqwkn89469s7 小时前
如果Visual Studio不支持C++14,应该如何解决?
c++·ide·visual studio