Linux 网络套接字编程(三)UDP服务器与客户端实现:Windows与Linux通信,新增字典翻译功能的 UDP 通信

目录

一、Windows与Linux通信

[Windows 客户端环境(VS2022)](#Windows 客户端环境(VS2022))

[Linux 服务端环境(Ubuntu + Xshell)](#Linux 服务端环境(Ubuntu + Xshell))

​编辑

[运行结果 :](#运行结果 :)

[二、新增 Dict 字典翻译模块的 UDP 通信](#二、新增 Dict 字典翻译模块的 UDP 通信)

[Dict 类](#Dict 类)

[Dict 类整体结构](#Dict 类整体结构)

LoadDict():从文件加载字典数据

Translate():实现核心翻译逻辑

[EchoServer.hpp 文件中 UdpServer 类的改造](#EchoServer.hpp 文件中 UdpServer 类的改造)

[EchoServerMain.cc 服务端入口文件的改造](#EchoServerMain.cc 服务端入口文件的改造)

回调的体现

[运行结果 :](#运行结果 :)

代码:

​编辑

三、总结


我们接着上一篇文章我们写的 UDP 服务端与客户端的通信,之前我们所有的网络操作、调试、通信逻辑,全部都在 Linux 环境下完成。不管是本地回环、公网访问,还是不同 Linux 机器之间的互通。

**那 Windows 能不能和 Linux 互相通信?**Windows 电脑上的程序,能不能通过网络,直接给 Linux 机器发数据?

答案是:完全可以!而且只要遵循标准的 Socket 接口(不管是 Windows 还是 Linux),两者可以实现完全互通。

下面我们就来验证一下 :

一、Windows与Linux通信

下面我们搭建一个跨操作系统的真实通信环境。其中,客户端运行在 Windows 上,而服务端继续运行在 Linux 上。

Windows 客户端环境(VS2022)

我们在 Windows 平台下开发并运行客户端程序,用到的开发工具是 Visual Studio 2022,VS2022 是 Windows 平台下最主流、最强大的 IDE 之一,用于开发 C/C++等应用。它内置 Windows 特有的 Winsock 网络库,提供标准的 BSD Socket 接口封装。

这里我们直接把 Windows 下的代码复制粘贴过来使用 :

客户端程序:使用 C/C++ 编写,调用 socket()、sendto()、recvfrom() 等标准 Socket 接口。向 Linux 服务端的 公网 IP "124.222.191.171"+ 8080 端口 发送 UDP 数据

虽然 Windows 和 Linux 的底层网络子系统不同,但 Socket 接口层面实现了跨平台标准化。因此,我们写的客户端代码几乎不需要修改,就能在 Windows 上编译、运行、直接发数据给 Linux。

Linux 服务端环境(Ubuntu + Xshell)

服务端继续保持我们熟悉的 Linux 环境 : 通过 Xshell 远程连接 Linux 云服务器

Linux 服务端使用标准的 POSIX Socket,通过 bind、recvfrom、sendto 等接口接受客户端请求,不区分客户端操作系统,只根据 IP + 端口 匹配数据。

运行结果 :

我们先运行 Windows 下的 VS2022 的客户端,运行完后向 Linux 中的服务端输入"hello Linux" :

按下回车后,Linux 下的服务端收到了 Windows 下的客户端发来的消息 :

此时我们就完成了 Windows 与 Linux 之间的通信。

二、新增 Dict 字典翻译模块的UDP 通信

上一篇文章中我们实现了 UDP 服务端和客户端的通信,但是这种通信也仅完成了基础的网络 IO 回声交互:服务端只负责接收客户端消息、原样拼接前缀后回发,核心逻辑集中在网络收发本身,没有额外业务处理能力。

而在真实开发场景中,网络服务的核心价值是承载上层业务逻辑,网络 IO 仅作为数据传输通道。基于此,我们对原有 UDP 服务端架构进行分层升级:在原有的服务端网络 IO 模块上层,新增 Dict 字典翻译业务模块,形成清晰的两层架构:

  1. 底层:服务端模块,专注处理 UDP 网络 IO,负责接收客户端网络数据、向上层交付原始消息、接收上层处理结果并通过网络回发给客户端;

  2. 上层:Dict 翻译模块,专注实现业务逻辑,接收底层传递的原始消息,完成字典翻译计算后,将翻译结果回传给网络层。

升级后的数据流转链路严格遵循分层调用逻辑:客户端发送的消息,先由服务端完成网络层面的接收,交付给上层 Dict 模块进行翻译处理;翻译完成后,处理结果原路返回网络 IO 层,再由服务端通过 UDP 协议回发给客户端。

Dict 类

之前的 UDP 回声服务中,服务端收到数据后,直接在 Start() 函数里拼接字符串回发,业务和网络收发逻辑是耦合在一起的。如果想把回声改成其他功能,比如计算器、翻译器,就必须修改服务端的代码,导致扩展性很差。

所以为了解耦,我们新增了一个 Dict.hpp 文件,里面封装了一个的 Dict 类,专门负责字典翻译的业务逻辑,让网络层和业务层彻底分开。

Dict 类整体结构

LoadDict():从文件加载字典数据

Translate():实现核心翻译逻辑

Translate() 函数的返回值和参数类型都是 std::string,刚好和我们后面要定义的 callback_t 回调类型完全匹配。这意味着,我们可以直接把 Dict::Translate 作为回调函数,注入到 UdpServer 中,让网络层调用它来处理数据。

EchoServer.hpp 文件中 UdpServer 类的改造

EchoServer.hpp 文件是改动最大的文件,从纯回声服务改成了支持回调注入的通用 UDP 服务框架。

首先我们新增了回调类型定义,定义了一个函数签名:接收 std::string,返回 std::string。

同时新增了成员变量 callback_t _cb,用来保存注入的业务回调(也就是 Dict::Translate),让 Start() 循环里能随时调用。

同时也要修改构造函数,支持注入回调,新增了 callback_t cb 参数,把上层业务逻辑 "注入" 到网络层,_cb 作为成员变量保存,后面 Start() 中调用。

Start() 方法替换回声逻辑为回调调用,原来收到数据后,直接拼接字符串回发,现在收到数据后,先调用 _cb 处理数据,拿到处理结果再回发,这样网络层就和具体业务无关了,只负责收发和调用回调,从而实现解耦。

EchoServerMain.cc 服务端入口文件的改造

EchoServerMain.cc 文件绑定了网络层和业务层,是把 Dict 和 UdpServer 连起来的胶水代码。

首先定义了一个 dict 对象,用 Lambda 表达式捕获 dict 对象,把 dict.Translate() 适配成 callback_t 类型,这样 UdpServer 启动后,每次收到数据都会调用通过 _cb 来回调这个 Lambda,从而间接调用 dict.Translate() 是西安翻译功能。

EchoClient.cc 客户端文件不做任何改动,因为客户端只负责发字符串、收字符串,它本不知道服务端现在是回声还是翻译,网络协议(UDP)、数据格式(字符串)没变,所以客户端不用改。

回调的体现

先定义回调函数类型,定义一个叫 callback_t 的 "函数类型",要求这个函数必须:接收 1 个 std::string 参数,返回 1 个 std::string 结果。

这里的 _cb(cb) 就是关键:我们创建 UdpServer 时,会把一个符合 callback_t 类型的函数传进来

构造函数把这个函数保存到成员变量 _cb 里,从此,UdpServer 里就存了一个 "未来要调用的业务函数",但现在还没执行。

服务端收到数据时:调用回调函数处理数据,网络层 UdpServer 收到数据后,不自己处理,而是调用 _cb,把收到的 inbuffer 传给 _cb,拿到返回的 result,再把 result 回发给客户端,这一步里,UdpServer 根本不知道 _cb 里面是什么逻辑,它只知道:"我收到数据,按标准格式调用 _cb,拿到结果发回去就行。"

这里是把 Dict 翻译逻辑,包装成符合 callback_t 标准的函数,传给 UdpServer:用一个 Lambda 表达式捕获 dict 对象,Lambda 的签名是 std::string(std::string),刚好匹配 callback_t,里面实际执行的是 dict.Translate(word),把翻译结果返回,当 UdpServer 调用 _cb(inbuffer) 时,实际执行的就是这个 Lambda,再由 Lambda 去调用 dict.Translate。

这就是回调函数的 "本体":接收 Lambda 传过来的 word,在 _dict 里查找单词,返回中文释义或 "None"

数据流转链路

客户端发送 apple → 网络 → 服务端→ 服务端 UdpServer::recvfrom 收到数据 → 调用注入的回调 dict.Translate("apple") → Translate 方法返回结果 → UdpServer::sendto 把结果回发给客户端 → 客户端收到并打印

运行结果 :

因为执行服务端是定义了 dict 对象,所以会先执行 dict 的构造函数,构造函数里又有加载函数,加载函数会先通过日志打印出字典文件中的内容,加载完毕后继续向后执行,执行 Init()和 Start() 函数。

成功翻译!

代码:

Dict.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Logger.hpp"

static const std::string default_dict = "./dict.txt";
static const std::string sep = ": ";
using namespace NS_LOG_MODULE;

class Dict
{
public:
    Dict(const std::string &dict_path = default_dict) : _dict_path(dict_path)
    {
        LoadDict();
    }
    ~Dict()
    {
    }
    void LoadDict()
    {
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::FATAL) << " open " << _dict_path << " error";
            exit(1);
        }
        std::string line;
        while (std::getline(in, line))
        {
            LOG(LogLevel::FATAL) << "load: " << line << " success";
            // apple: 苹果
            auto pos = line.find(sep);
            if (pos == std::string::npos)
            {
                LOG(LogLevel::WARNING) << "Format: " << line << " error";
                continue;
            }

            std::string k = line.substr(0, pos); // [)
            std::string v = line.substr(pos + sep.size());
            _dict.insert(std::make_pair(k, v));
        }

        in.close();
        LOG(LogLevel::INFO) << "load done....";
    }
    std::string Translate(std::string word)
    {
        auto iter = _dict.find(word);
        if(iter != _dict.end())
        {
            return iter->second;
        }
        else
        {
            return "None";
        }
    }

private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
};


class Dict 
{
public:
    Dict(const std::string &dict_path = default_dict)
    : _dict_path(dict_path)
    {
        LoadDict();
    }

    void LoadDict()
    {

    }
    
    std::string Translate(std::string word)
    {

    }
private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
};

EchoClient.cc 客户端:

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
        
static void Usage(const std::string &proc)
{
    std::cout << "Usage:\n\t";
    std::cout << proc << " server_ip server_port" << std::endl;
}

// 我怎么知道server对方的IP和端口啊, 类似IP+Port 是被内置到client的!!!
// ./client_udp server_ip server_port
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);


    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }

    // 2. 构建server端socket信息 
    // client 需要有自己的IP和Port信息吗? 需要的!
    // 需要显示的bind自己的ip和端口吗?不要显示bind!!!
    // 1. 为什么不让client显示bind?client bind port 出现冲突!client port只需要具有唯一性即可,具体是几,不重要。
    // 2. 如何设置自己的IP和端口呢?client 一般会采用随机端口的方式!由OS自主选择!
    // udp client 首次发送数据的时候,OS底层会隐式自动帮你进行获取随机端口,然后bind + Port + IP
    struct sockaddr_in server;
    memset(&server, 0, sizeof(0));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port); 
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    while(true)
    {
        std::string message;
        // 1. 获取用户输入
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        // 2. clinet 发送数据给 server,首次发送即自动bind
        ssize_t n = sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
        if(n > 0)
        {
            // recvfrom
            char inbuffer[1024] = {0};
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            ssize_t m = recvfrom(sockfd, inbuffer, sizeof(inbuffer)-1, 0, (struct sockaddr*)&temp, &len);
            if(m > 0)
            {
                inbuffer[m] = 0;
                std::cout << inbuffer << std::endl;
            }
        }
    }


    return 0;
}

EchoServer.hpp 服务端:

cpp 复制代码
#ifndef __ECHOSERVER_HPP
#define __ECHOSERVER_HPP

#include <iostream>
#include <string>
#include <cstdlib>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <functional>
#include <arpa/inet.h>

#include "Logger.hpp"

using namespace NS_LOG_MODULE;

const static int default_fd = -1;
const static int default_port = 8888;

using callback_t = std::function<std::string(std::string)>;

enum
{
    SUCCESS = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
};

class UdpServer
{
public:
    // UdpServer(const std::string &ip, uint16_t port = default_port)
    UdpServer(callback_t cb, uint16_t port = default_port)
        : _port(port),
          _sockfd(default_fd),
          _cb(cb)
    {
    }
    ~UdpServer()
    {
        close(_sockfd);
    }
    void Init()
    {
        // 第一步: 创建socket, 本质: 打开网卡 --- 系统特性
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "create socket success, sockfd: " << _sockfd;

        // 第二步: 填充网络信息, 有没有IP和端口信息设置到内核中??设置到你刚刚打开的网络socket对应的文件内部?
        struct sockaddr_in local; // struct sockaddr_in 数据类型,local 用户栈上的!!!,并没有设置到内核
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);                  // h->n
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 字符串ip->4字节IP 2. hton
        local.sin_addr.s_addr = INADDR_ANY; // 最佳实践:任意IP地址bind

        // 第三步:bind socket 信息
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind socket error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind socket success"<< ", port: " << _port;
    }
    void Start()
    {
        // 传递的是字符串,echo server
        char inbuffer[1024];
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 用户发来的数据
            // 2. 用户的socket信息
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                // ?
                // 用户的IP + Port信息,是recvfrom从网络中获取到的数据,网络序列(大端)-> 转换成为主机序列
                // peer.sin_port; // peer client port
                // peer.sin_addr; // peer client ip
                // uint16_t client_port = ntohs(peer.sin_port);
                // std::string client_ip = inet_ntoa(peer.sin_addr); // 4字节IP-> ntoh -> 字符串
                // std::string client_address = "[" + client_ip + ":" + std::to_string(client_port) + "]# ";

                // 用户发来的数据
                inbuffer[n] = 0;
                // LOG(LogLevel::DEBUG) << client_address << inbuffer;
                // std::string echo_string = "server echo# ";
                // echo_string += inbuffer;
                std::string result = _cb(inbuffer); // 单词

                // h to n
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                LOG(LogLevel::ERROR) << "recvfrom error";
            }
        }
    }

private:
    int _sockfd;
    // std::string _ip; // "192.168.2.2"(字符串风格的点分十进制IP地址, 让人看的) && 4字节IP ???
    uint16_t _port;  // 用户设置好的,server port必须是固定的!
    callback_t _cb; // 用回调的方式进行数据加工
};

#endif

EchoServerMain.cc 服务端:

cpp 复制代码
#include "Dict.hpp"
#include "EchoServer.hpp"
#include <memory>

static void Usage(const std::string &process)
{
    std::cerr << "Usage:\n\t";
    std::cerr << process << " local_port" << std::endl;
}

// ./server_udp port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    // 1. 定义字典
    Dict dict;
    // 2. 构建网络服务,处理IO问题
    uint16_t server_port = std::stoi(argv[1]);
    // 3. 绑定上下两层
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&dict](std::string word)->std::string{
        return dict.Translate(word);
    }, 
    server_port);
    usvr->Init();
    usvr->Start();

    return 0;
}

Logger.hpp:

cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
#include "Mutex.hpp"

namespace NS_LOG_MODULE
{
    enum class LogLevel
    {
        INFO,
        WARNING,
        ERROR,
        FATAL,
        DEBUG
    };
    std::string LogLevel2Message(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        case LogLevel::DEBUG:
            return "DEBUG";
        default:
            return "UNKNOWN";
        }
    }

    // 1. 时间戳 2. 日期+时间
    std::string GetCurrentTime()
    {
        struct timeval current_time;
        int n = gettimeofday(&current_time, nullptr);
        (void)n;

        // current_time.tv_sec; current_time.tv_usec;
        struct tm struct_time;
        localtime_r(&(current_time.tv_sec), &struct_time); // r: 可重入函数
        char timestr[128];
        snprintf(timestr, sizeof(timestr), "%04d-%02d-%02d %02d:%02d:%02d.%ld",
                 struct_time.tm_year + 1900,
                 struct_time.tm_mon + 1,
                 struct_time.tm_mday,
                 struct_time.tm_hour,
                 struct_time.tm_min,
                 struct_time.tm_sec,
                 current_time.tv_usec);
        return timestr;
    }

    // 输出角度 -- 刷新策略
    // 1. 显示器打印
    // 2. 文件写入
    // 策略模式,策略接口
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };
    // 控制台日志刷新策略, 日志将来要向显示器打印
    class ConsoleStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cerr << message << std::endl; // ??
        }
        ~ConsoleStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log";
    const std::string defaultfilename = "log.txt";


    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
            : _logpath(path),
              _logfilename(name)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            {
                LockGuard lockguard(_mutex);
                if (!_logpath.empty() && _logpath.back() != '/')
                {
                    _logpath += "/";
                }
                std::string targetlog = _logpath + _logfilename; // "./log/log.txt"
                std::ofstream out(targetlog, std::ios::app);     // 追加方式写入
                if (!out.is_open())
                {
                    std::cerr << "open " << targetlog << "failed" << std::endl;
                    return;
                }
                out << message << "\n";
                out.close();
            }
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _logpath;
        std::string _logfilename;
        Mutex _mutex;
    };

    // 日志类:
    // 1. 日志的生成
    // 2. 根据不同的策略,进行刷新
    class Logger
    {
        // 日志的生成:
        // 构建日志字符串
    public:
        Logger()
        {
            UseConsoleStrategy();
        }
        void UseConsoleStrategy()
        {
            _strategy = std::make_unique<ConsoleStrategy>();
        }
        void UseFileStrategy()
        {
            _strategy = std::make_unique<FileLogStrategy>();
        }
        // 内部类, 标识一条完整的日志信息
        //  一条完整的日志信息 = 做半部分固定部分 + 右半部分不固定部分
        //  LogMessage RAII风格的方式,进行刷新
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
                : _level(level),
                  _curr_time(GetCurrentTime()),
                  _pid(getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                // 先构建出来左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LogLevel2Message(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] "
                   << " - ";

                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this; // 返回当前LogMessage对象,方便下次继续进行<<
            }

            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            LogLevel _level;
            std::string _curr_time;
            pid_t _pid;
            std::string _filename;
            int _line;
            std::string _loginfo; // 一条完整的日志信息

            // 一个引用,引用外部的Logger类对象
            Logger &_logger; // 方便我们后续进行策略式刷新
        };

        // 这里已经不是内部类了
        // 故意采用拷贝LogMessage
        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _strategy; // 刷新策略
    };

    // 日志对象,全局使用
    Logger logger;

#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy();
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy();

#define LOG(level) logger(level, __FILE__, __LINE__)

}

#endif

Makefile:

cpp 复制代码
.PHONY:all
all:client_udp server_udp

server_udp:EchoServerMain.cc
	g++ -o $@ $^ -std=c++17
client_udp:EchoClient.cc
	g++ -o $@ $^ -std=c++17 -static

.PHONY:clean
clean:
	rm -f client_udp server_udp

Mutex.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&_lock);
    }
    pthread_mutex_t *Ptr()
    {
        return &_lock;
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};

class LockGuard // RAII风格代码
{
public:
    LockGuard(Mutex &lock):_lockref(lock)
    {
        _lockref.Lock();
    }
    ~LockGuard()
    {
        _lockref.Unlock();
    }
private:
    Mutex &_lockref;
};

dict.txt:

cpp 复制代码
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
fall: 秋天
Liz: 栗子

三、总结

本文验证了Windows与Linux系统间基于UDP协议的网络通信可行性,并实现了字典翻译功能的服务端分层架构改造。主要内容包括:

  1. 跨平台通信验证:Windows客户端通过VS2022开发,调用标准Socket接口与Linux服务端成功实现UDP通信,证明不同操作系统间网络互通的可行性。

  2. 服务端架构升级:

  • 将原有回声服务改造为两层架构:底层网络IO模块负责数据传输,上层Dict模块专注业务逻辑
  • 通过回调机制实现解耦,网络层调用注入的翻译函数处理数据
  • 新增Dict类实现字典加载和单词翻译功能
  1. 功能实现:
  • 客户端发送英文单词,服务端返回中文翻译
  • 采用日志模块记录运行状态
  • 通过Lambda表达式实现回调适配

代码结构清晰展示了网络层与业务层的分离,为后续功能扩展提供了良好基础。

谢谢大家的观看!

相关推荐
2401_836554222 小时前
服务器 Docker 部署 Hermes Agent → 飞书(零端口暴露)
服务器·docker·飞书
Robot_Nav2 小时前
Hybrid A* 算法文献解读
算法·路径规划·hybrid a
WolfGang0073212 小时前
代码随想录算法训练营 Day41 | 单调栈 part01
算法·动态规划
嵌入式×边缘AI:打怪升级日志2 小时前
DHT11 驱动开发实录:从零搭建 Linux 字符设备驱动框架(保姆级教学)
linux·运维·驱动开发
人道领域2 小时前
【Redis实战篇】秒杀系统:一人一单高并发实战(synchronized锁实战与事务失效问题)
java·开发语言·数据库·redis·spring
艾莉丝努力练剑2 小时前
【Linux网络】计算机网络入门:网络通信——跨主机的进程间通信(IPC)与Socket编程入门
linux·运维·服务器·网络·c++·学习·计算机网络
嘻嘻哈哈樱桃2 小时前
牛客经典101题解题集--二分查找/排序
数据结构·算法·职场和发展
0xDevNull2 小时前
Spring中统一异常处理详细教程
java·开发语言·后端