【Linux网络】构建UDP服务器与字典翻译系统

news2025/4/26 0:47:41

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、服务端更新
    • 1.1 函数对象声明
    • 1.2 UdpServer 类成员更新
    • 1.3 构造函数更新
    • 1.4 开始 - Start() 更新
  • 🏳️‍🌈二、Dictionary 字典类设计
    • 2.1 基本结构
    • 2.2 加载字典文件 - LoadDictionary(const std::string& path)
    • 2.3 构造函数
    • 2.4 翻译函数
    • 2.5 服务端运行更新
  • 🏳️‍🌈三、整体代码
  • 👥总结


上一篇文章中,我们实现了回显客户端输入的功能,这功能往往是不够的,为了更好地模拟现实需求,我们现在多增加一个功能 - 字典翻译功能

🏳️‍🌈一、服务端更新

1.1 函数对象声明

别的功能、成员名保持不变,为了新增字典翻译功能,我们需要引入函数对象类型

// 回调函数对象声明
using func_t = std::function<std::string(std::string)>;

1.2 UdpServer 类成员更新

class UdpServer : public nocopy{
    public:
		UdpServer(func_t func,uint16_t localport = glocalport);
	    void InitServer();
	    void Start();
	    ~UdpServer();
    private:
        int _sockfd;            // 文件描述符
        uint16_t _localport;    // 端口号
        std::string _localip;   // 本地IP地址
        bool _isrunning;        // 运行状态

        func_t _func;           // 回调函数
};

1.3 构造函数更新

  • 构造函数只需增加一个函数对象参数,初始化列表初始化变量即可!!!
UdpServer(uint16_t localport = gdefaultport, func_t func = nullptr)
    : _sockfd(gsockfd), _localport(localport), _isrunning(false), _func(func) {}

1.4 开始 - Start() 更新

  • 之前只需要回显的时候,我们直接接收客户端信息,将网络字节序的客户端ip和端口号转换为主机字节序,再返回就行了
  • 现在我们要在这之间添加一个环节,使收到的客户端信息,先通过字典翻译回调函数,将处理后的值传回去
void Start() {
    _isrunning = true;
    while (true) {
        char inbuffer[1024];              // 接收缓冲区
        struct sockaddr_in peer;          // 接收客户端地址
        socklen_t peerlen = sizeof(peer); // 计算接收的客户端地址长度

        // 接收数据报
        // recvfrom(int sockfd, void* buf, size_t len, int flags, struct
        // sockaddr* src_addr, socklen_t* addrlen)
        // 从套接字接收数据,并存入buf指向的缓冲区中,返回实际接收的字节数
        // 参数sockfd:套接字文件描述符
        // 参数buf:指向接收缓冲区的指针,c_str()函数可以将字符串转换为char*,以便存入缓冲区
        // 参数len:接收缓冲区的长度
        // 参数flags:接收标志,一般设为0
        // 参数src_addr:指向客户端地址的指针,若不为NULL,函数返回时,该指针指向客户端的地址,是网络字节序
        // 参数addrlen:客户端地址长度的指针,若不为NULL,函数返回时,该指针指向实际的客户端地址长度
        ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0,
                               CONV(&peer), &peerlen);
        if (n > 0) {
            // 将英文单词 转换为 中文
            std::string result = _func(inbuffer);
            ::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer),
                     peerlen);
        }
    }
}

🏳️‍🌈二、Dictionary 字典类设计

字典类执行加载字典文件 和 执行翻译的功能

2.1 基本结构

class Dictionary{
    private:
        // 加载字典文件
        void LoadDictionary(const std::string& path);
    public:
        // 构造函数
        Dictionary(const std::string& path);
        // 翻译
        std::string Translate(const std::string& word);
        // 析构函数 
        ~Dictionary();
    private:
        std::unordered_map<std::string, std::string> _dict;     // 字典结构
        std::string _dict_path;                                 // 文件路径
};

2.2 加载字典文件 - LoadDictionary(const std::string& path)

我们以 ": " 一个冒号加一个空格的形式,进行翻译

  • 加载字典文件的本质是以KV的形式将英文单词和中文翻译插入到_dict哈希表中!

  • 加载文件包含3个大的步骤

    1. 读方式打开文件
    2. 按行读取内容[需要考虑中间有空格情况,一行中没找到分隔符情况]
    3. 关闭文件
// 加载字典文件
void LoadDictionary(const std::string& path) {
    // 1. 读方式打开文件
    std::ifstream in(path);
    if (!in.is_open()) {
        LOG(LogLevel::FATAL) << "open " << path.c_str() << " failed";
        Die(1);
    }
    std::string line;

    // 2. 按行读取内容
    while (std::getline(in, line)) {
        LOG(LogLevel::DEBUG) << line.c_str() << "load success";
        if (line.empty())
            continue; // 中间有空格情况

        auto pos = line.find(sep); // 使用find找到分隔符位置,返回迭代器位置
        if (pos == std::string::npos)
            continue; // 找不到分隔符,跳过该行

        std::string key = line.substr(0, pos); // 前闭后开
        if (key.empty())
            continue; // 键为空,跳过该行

        std::string value = line.substr(pos + sep.size());
        if (value.empty())
            continue; // 值为空,跳过该行

        _dict.insert(std::make_pair(key, value));
    }
    LOG(LogLevel::INFO) << path.c_str() << " load success";

    // 3. 关闭文件
    in.close();
}

2.3 构造函数

初始化字典文件,并将键值对加载到本地保存

// 构造函数
Dictionary(const std::string& path = gpath + gdictname) { LoadDictionary(path); }

2.4 翻译函数

在键值对中查找是否有该单词,有单词就返回值,没有返回None

// 翻译
std::string Translate(const std::string& word) {
    auto iter = _dict.find(word);
    if (iter == _dict.end())
        return "None";
    return iter->second;
}

2.5 服务端运行更新

因为我们现在需要将字典类的查找方法 作为回调函数传给服务端 ,所以需要进行一些变化

#include "UdpServer.hpp"
#include "Dictionary.hpp"

int main(int argc, char *argv[])
{
    if(argc != 2){
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(1);
    }

    uint16_t port = std::stoi(argv[1]);

    ENABLE_CONSOLE_LOG();   // 日期类方法,使日志在控制台输出

    std::shared_ptr<Dictionary> dict_ptr = std::make_shared<Dictionary>();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict_ptr]( const std::string& word){
        std::cout << "|" << word << "|" << std::endl;
        return dict_ptr->Translate(word);
    });
  
 
    usvr->InitServer(); // 初始化服务端
    usvr->Start();      // 启动服务端
    return 0;
}

在这里插入图片描述

🏳️‍🌈三、整体代码

UdpServer.hpp

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <functional>
#include <cerrno>   // 这个头文件包含了errno定义,用于存放系统调用的返回值
#include <strings.h>    // 属于POSIX扩展​(非标准C/C++),常见于Unix/Linux系统,提供额外字符串函数(如 bcopy, bzero)

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const static int gsockfd = -1;
const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

// 回调函数对象声明
using func_t = std::function<std::string(std::string)>;


class nocopy{
    public:
        nocopy(){}
        ~nocopy(){}
        nocopy(const nocopy&) = delete;     // 禁止拷贝构造函数
        const nocopy& operator=(const nocopy&) = delete;   // 禁止拷贝赋值运算符
};


class UdpServer : public nocopy{
    public:
        UdpServer(uint16_t localport = gdefaultport, func_t func = nullptr)
            : _sockfd(gsockfd),
              _localport(localport),
              _isrunning(false),
              _func(func)
        {}
        void InitServer(){
            // 1. 创建套接字
            // socket(int domain, int type, int protocol)
            // 返回一个新的套接字文件描述符,或者在出错时返回-1
            // 参数domain:协议族,AF_INET,表示IPv4协议族
            // 参数type:套接字类型,SOCK_DGRAM,表示UDP套接字
            // 参数protocol:协议,0,表示默认协议
            _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
            if(_sockfd < 0){
                LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
                // exit(SOCKET_ERR) 表示程序运行失败,并返回指定的错误码
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::DEBUG) << "socket success, sockfd is: " << _sockfd;


            // 2. bind
            // sockaddr_in 
            struct sockaddr_in local;
            // 将local全部置零,以便后面设置
            memset(&local, 0, sizeof(local)); 
            local.sin_family = AF_INET; // IPv4协议族
            local.sin_port = htons(_localport); // 端口号,网络字节序
            local.sin_addr.s_addr = htonl(INADDR_ANY); // 本地IP地址,网络字节序

            // 将套接字绑定到本地地址
            // bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
            // 绑定一个套接字到一个地址,使得套接字可以接收来自该地址的数据报
            // 参数sockfd:套接字文件描述符
            // 参数addr:指向sockaddr_in结构体的指针,表示要绑定的地址
            // 参数addrlen:地址长度,即sizeof(sockaddr_in)
            // 返回0表示成功,-1表示出错
            int n = ::bind(_sockfd, (struct sockaddr* )&local, sizeof(local));
            if(n < 0){
                LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
                exit(BIND_ERR);
            }
            LOG(LogLevel::DEBUG) << "bind success";
        }
        void Start(){
            _isrunning = true;
            while(true){
                char inbuffer[1024];                // 接收缓冲区
                struct sockaddr_in peer;            // 接收客户端地址
                socklen_t peerlen = sizeof(peer);   // 计算接收的客户端地址长度

                // 接收数据报
                // recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen)
                // 从套接字接收数据,并存入buf指向的缓冲区中,返回实际接收的字节数
                // 参数sockfd:套接字文件描述符
                // 参数buf:指向接收缓冲区的指针,c_str()函数可以将字符串转换为char*,以便存入缓冲区
                // 参数len:接收缓冲区的长度
                // 参数flags:接收标志,一般设为0
                // 参数src_addr:指向客户端地址的指针,若不为NULL,函数返回时,该指针指向客户端的地址,是网络字节序
                // 参数addrlen:客户端地址长度的指针,若不为NULL,函数返回时,该指针指向实际的客户端地址长度
                ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &peerlen);
                if(n > 0){
                    // 将英文单词 转换为 中文
                    std::string result = _func(inbuffer);
                    ::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), peerlen);
                }
            }
        }
        ~UdpServer(){
            // 判断 _sockfd 是否是一个有效的套接字文件描述符
            // 有效的文件描述符(如套接字、打开的文件等)是非负整数​(>= 0)
            if(_sockfd > -1) ::close(_sockfd);
        }
    private:
        int _sockfd;            // 文件描述符
        uint16_t _localport;    // 端口号
        std::string _localip;   // 本地IP地址
        bool _isrunning;        // 运行状态

        func_t _func;           // 回调函数
};

UdpServer.cpp

#include "UdpServer.hpp"
#include "Dictionary.hpp"

int main(int argc, char *argv[])
{
    if(argc != 2){
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(1);
    }

    uint16_t port = std::stoi(argv[1]);

    ENABLE_CONSOLE_LOG();   // 日期类方法,使日志在控制台输出

    std::shared_ptr<Dictionary> dict_ptr = std::make_shared<Dictionary>();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict_ptr]( const std::string& word){
        std::cout << "|" << word << "|" << std::endl;
        return dict_ptr->Translate(word);
    });
  
 
    usvr->InitServer(); // 初始化服务端
    usvr->Start();      // 启动服务端
    return 0;
}

UdpClient.hpp

#pragma once

#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

UdpClient.cpp

#include "UdpClient.hpp"

int main(int argc, char* argv[]){
    if(argc != 3){
        std::cerr << argv[0] << " serverip server" << std::endl;
        Die(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0){
        std::cerr << "create socket error" << std::endl;
        Die(SOCKET_ERR);
    }

    // 1. 填充 server 信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = ::htons(serverport);
    server.sin_addr.s_addr = ::inet_addr(serverip.c_str());

    // 2. 发送数据
    while(true){
        std::cout << "Please Enter# ";
        std::string msg;
        std::getline(std::cin, msg);
        
        // client 必须自己的ip和端口。但是客户端,不需要显示调用bind
        // 客户端首次 sendto 消息的时候,由OS自动bind
        // 1. 如何理解 client 自动随机bind端口号? 一个端口号,只能读一个进程bind
        // 2. 如何理解 server 要显示地bind? 必须稳定!必须是众所周知且不能轻易改变的
        int n = ::sendto(sockfd, msg.c_str(), msg.size(), 0, CONV(&server), sizeof(server));
        (void)n;

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        n = ::recvfrom(sockfd, buffer,sizeof(buffer) - 1, 0, CONV(&temp), &len);
        if(n > 0){
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }
    return 0;
}

Common.hpp

#pragma once

#include <iostream>


#define Die(code)   \
    do              \
    {               \
        exit(code); \
    } while (0)

#define CONV(v) (struct sockaddr *)(v)

enum 
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

InetAddr.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"

class InetAddr
{
private:
    void PortNet2Host()
    {
        _port = ::ntohs(_net_addr.sin_port);
    }
    void IpNet2Host()
    {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        (void)ip;
    }

public:
    InetAddr(){}
    InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
    {
        PortNet2Host();
        IpNet2Host();
    }
    InetAddr(uint16_t port) : _port(port), _ip("")
    {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = INADDR_ANY;
    }
    struct sockaddr *NetAddr() { return CONV(&_net_addr); }
    socklen_t NetAddrLen() { return sizeof(_net_addr); }
    std::string Ip() { return _ip; }
    uint16_t Port() { return _port; }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _net_addr;
    std::string _ip;
    uint16_t _port;
};

Dictionary.hpp

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const static std::string sep = ": ";
const static std::string gpath = "./";
const static std::string gdictname = "dict.txt";


class Dictionary{
    private:
        // 加载字典文件
        void LoadDictionary(const std::string& path){
            // 1. 读方式打开文件
            std::ifstream in(path);
            if(!in.is_open()){
                LOG(LogLevel::FATAL) << "open " << path.c_str() << " failed";
                Die(1);
            }
            std::string line;

            // 2. 按行读取内容
            while(std::getline(in, line)){
                LOG(LogLevel::DEBUG) << line.c_str() << "load success";
                if(line.empty())
                    continue; // 中间有空格情况

                auto pos = line.find(sep);  // 使用find找到分隔符位置,返回迭代器位置
                if(pos == std::string::npos)
                    continue; // 找不到分隔符,跳过该行

                std::string key = line.substr(0, pos); // 前闭后开
                if(key.empty())
                    continue; // 键为空,跳过该行

                std::string value = line.substr(pos + sep.size()); 
                if(value.empty())
                    continue; // 值为空,跳过该行

                _dict.insert(std::make_pair(key, value));
            }
            LOG(LogLevel::INFO) << path.c_str() << " load success";
            
            // 3. 关闭文件
            in.close(); 
        }
    public:
        // 构造函数
        Dictionary(const std::string& path = gpath + gdictname){
            LoadDictionary(path);
        }
        // 翻译
        std::string Translate(const std::string& word){
            auto iter = _dict.find(word);
            if(iter == _dict.end()) return "None";
            return iter->second;
        }
        // 析构函数 
        ~Dictionary(){}
    private:
        std::unordered_map<std::string, std::string> _dict;     // 字典结构
        std::string _dict_path;                                 // 文件路径
};

Log.hpp

#pragma once

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

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);

        return buffer;
    }

    // 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
    //  1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }

    // 3. 刷新策略.
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        ~ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }

    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(logpath),
              _logname(logname)
        {
            // 确认_logpath是存在的.
            LockGuard lockguard(_lock);

            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        ~FileLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::string log = _logpath + _logname; // ./log/log.txt
            std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }

    private:
        std::string _logpath;
        std::string _logname;

        // 锁
        Mutex _lock;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger() {}
        // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                : _currtime(CurrentTime()),
                  _level(level),
                  _pid(::getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

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

        private:
            std::string _currtime; // 当前日志的时间
            LogLevel _level;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _filename; // 源文件名称
            int _line;             // 日志所在的行号
            Logger &_logger;       // 负责根据不同的策略进行刷新
            std::string _loginfo;  // 一条完整的日志记录
        };

        // 就是要拷贝,故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

    private:
        std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

Mutex.hpp

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

namespace LockModule
{
    class Mutex
    {
    public:
        Mutex(const Mutex&) = delete;
        const Mutex& operator = (const Mutex&) = delete;
        Mutex()
        {
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n;
        }
        ~Mutex()
        {
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n;
        }
        void Lock()
        {
            int n = ::pthread_mutex_lock(&_lock);
            (void)n;
        }
        pthread_mutex_t *LockPtr()
        {
            return &_lock;
        }
        void Unlock()
        {
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n;
        }

    private:
        pthread_mutex_t _lock;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mtx):_mtx(mtx)
        {
            _mtx.Lock();
        }
        ~LockGuard()
        {
            _mtx.Unlock();
        }
    private:
        Mutex &_mtx;
    };
}

Makefile

.PHONY: all
all:server_udp client_udp

server_udp:UdpServer.cpp
	g++ -o $@ $^ -std=c++17
	
client_udp:UdpClient.cpp 
	g++ -o $@ $^ -std=c++17

.PHONY: clean
clean:
	rm -f server_udp client_udp

👥总结

本篇博文对 【Linux网络】构建Udp服务器与字典翻译系统 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2342816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PGCCC】Postgres 故障排除:修复重复的主键行

如何从表中删除不需要的重复行。这些重复行之所以“不需要”&#xff0c;是因为同一个值在指定为主键的列中出现多次。自从 glibc 好心地改变了排序方式后&#xff0c;我们发现这个问题有所增加。当用户升级操作系统并修改底层 glibc 库时&#xff0c;这可能会导致无效索引。 唯…

DeepSeek+Cursor+Devbox+Sealos项目实战

黑马程序员DeepSeekCursorDevboxSealos带你零代码搞定实战项目开发部署视频教程&#xff0c;基于AI完成项目的设计、开发、测试、联调、部署全流程 原视频地址视频选的项目非常基础&#xff0c;基本就是过了个web开发流程&#xff0c;但我在实际跟着操作时&#xff0c;ai依然会…

996引擎-拓展变量:物品变量

996引擎-拓展变量:物品变量 测试代码参考资料对于Lua来说,只有能保存数据库的变量才有意义。 至于临时变量,不像TXT那么束手束脚,通常使用Lua变量就能完成。 SELECT * FROM dbo.TBL_ITEM_EX_ABIL WHERE FLD_MAKEINDEX = 28620 <

【踩坑记录】stm32 jlink程序烧录不进去

最近通过Jlink给STM32烧写程序时一直报错&#xff0c;但是换一个其他工程就可以烧录&#xff0c;对比了一下jink配置&#xff0c;发现是速率选太高了“SW Device”&#xff0c;将烧录速率调整到10MHz以下就可以了

‌RISC-V低功耗MCU动态时钟门控技术详解

我来分享一下RISC-V核低功耗MCU的动态时钟门控技术实现&#xff1a; 这款MCU通过硬件级时钟门控电路实现了模块级的功耗管理。当外设&#xff08;如UART、SPI&#xff09;处于闲置状态时&#xff0c;系统会自动切断其时钟信号&#xff0c;减少无效翻转功耗。同时支持多电压域协…

工厂模式:解耦对象创建与使用的设计模式

工厂模式&#xff1a;解耦对象创建与使用的设计模式 一、模式核心&#xff1a;封装对象创建逻辑&#xff0c;客户端无需关心具体实现 在软件开发中&#xff0c;当创建对象的逻辑复杂或频繁变化时&#xff0c;直接在客户端代码中 new 对象会导致耦合度高、难以维护。例如&…

Python爬虫学习:高校数据爬取与可视化

本项目实现了从中国教育在线&#xff08;eol.cn&#xff09;的公开 API 接口爬取高校相关数据&#xff0c;并对数据进行清洗、分析与可视化展示。主要包括以下功能&#xff1a; 爬取高校基础信息及访问量数据数据清洗与格式转换多维度数据分析与可视化&#xff0c;如高校数量分…

触觉智能RK3506核心板,工业应用之RK3506 RT-Linux实时性测试

在工业自动化、机械臂控制等高实时性场景中&#xff0c;系统响应速度与稳定性直接决定设备效能。触觉智能RK3506核心板基于瑞芯微三核Cortex-A7架构深度优化&#xff0c;搭载Linux 6.1内核并支持Linux-RT实时系统&#xff0c;提供实时性能的高性价比解决方案。 RK3506与RT-Linu…

基于SpringBoot的高校体育馆场地预约管理系统-项目分享

基于SpringBoot的高校体育馆场地预约管理系统-项目分享 项目介绍项目摘要目录总体功能图用户实体图赛事实体图项目预览用户个人中心医生信息管理用户管理场地信息管理登录 最后 项目介绍 使用者&#xff1a;管理员 开发技术&#xff1a;MySQLJavaSpringBootVue 项目摘要 随着…

华为云获取IAM用户Token的方式及适用分析

&#x1f9e0; 一、为什么要获取 IAM 用户 Token&#xff1f; 我们用一个生活中的比喻来解释&#x1f447;&#xff1a; &#x1f3e2; 比喻场景&#xff1a; 你要去一个 高级写字楼&#xff08;华为云物联网平台&#xff09; 办事&#xff08;调用接口管理设备&#xff09;&…

如何利用快照与备份快速恢复服务器的数据

在服务器上利用**快照&#xff08;Snapshot&#xff09;**和**备份&#xff08;Backup&#xff09;**快速恢复数据&#xff0c;可显著减少停机时间并确保业务连续性。以下是具体操作步骤和最佳实践&#xff1a; --- ### **1. 快照&#xff08;Snapshot&#xff09;恢复** **适…

Git 详细使用说明文档(适合小白)

Git 详细使用说明文档&#xff08;适合小白&#xff09; 1. 什么是 Git&#xff1f; Git 是一个版本控制系统&#xff0c;帮助你管理和跟踪代码的变更。无论是个人项目还是团队协作&#xff0c;Git 都能帮助你记录代码的历史版本&#xff0c;方便回溯和协作。 2. 安装 Git …

Graph Database Self-Managed Neo4j 知识图谱存储实践1:安装和基础知识学习

Neo4j 是一个原生图数据库&#xff0c;这意味着它在存储层实现了真正的图模型。它不是在其他技术之上使用“图抽象”&#xff0c;而是以您在白板上绘制想法的相同方式在Neo4j中存储数据。 自2007年以来&#xff0c;Neo4j已经发展成为一个丰富的工具、应用程序和库的生态系统。…

一天学完Servlet!!!(万字总结)

文章目录 前言Servlet打印Hello ServletServlet生命周期 HttpServletRequest对象常用api方法请求乱码问题请求转发request域对象 HttpServletResponse对象响应数据响应乱码问题请求重定向请求转发与重定向区别 Cookie对象Cookie的创建与获取Cookie设置到期时间Cookie注意点Cook…

E3650工具链生态再增强,IAR全面支持芯驰科技新一代旗舰智控MCU

近日&#xff0c;全球嵌入式软件开发解决方案领导者IAR与全场景智能车芯引领者芯驰科技正式宣布&#xff0c;IAR Embedded Workbench for Arm已全面支持芯驰E3650&#xff0c;为这一旗舰智控MCU提供开发和调试一站式服务&#xff0c;进一步丰富芯驰E3系列智控芯片工具链生态&am…

MSSQL-数据库还原报错-‘32(另一个程序正在使用此文件,进程无法访问。)‘

这里是引用 标题: Microsoft SQL Server Management Studio 还原 对于 服务器“<<服务器名称>>”失败。 (Microsoft.SqlServer.SmoExtended) 有关帮助信息&#xff0c;请单击: http://go.microsoft.com/fwlink?ProdNameMicrosoftSQLServer&ProdVer12.0.2000.8…

卷积神经网络:视觉炼金术士的数学魔法

引言&#xff1a;当数学遇见视觉炼金术 在人工智能的奇幻世界里&#xff0c;卷积神经网络&#xff08;CNN&#xff09;犹如掌握视觉奥秘的炼金术士&#xff0c;将原始像素的"铅块"淬炼成认知的"黄金"。这种融合数学严谨性与生物灵感的算法架构&#xff0c…

立马耀:通过阿里云 Serverless Spark 和 Milvus 构建高效向量检索系统,驱动个性化推荐业务

作者&#xff1a;厦门立马耀网络科技有限公司大数据开发工程师 陈宏毅 背景介绍 行业 蝉选是蝉妈妈出品的达人选品服务平台。蝉选秉持“陪伴达人赚到钱”的品牌使命&#xff0c;致力于洞悉达人变现需求和痛点&#xff0c;提供达人选高佣、稳变现、速响应的选品服务。 业务特…

专业热度低,25西电光电工程学院(考研录取情况)

1、光电工程学院各个方向 2、光电工程学院近三年复试分数线对比 学长、学姐分析 由表可看出&#xff1a; 1、光学工程25年相较于24年下降20分&#xff0c; 2、光电信息与工程&#xff08;专硕&#xff09;25年相较于24年上升15分 3、25vs24推免/统招人数对比 学长、学姐分析…

java—11 Redis

目录 一、Redis概述 二、Redis类型及编码 三、Redis对象的编码 1. 类型&编码的对应关系 2. string类型常用命令 &#xff08;1&#xff09;string类型内部实现——int编码 &#xff08;2&#xff09;string类型内部实现——embstr编码 ​编辑 &#xff08;3&#x…