【Linux网络】Linux 网络编程入门:UDP Socket 编程(下)

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> UDP编程:三个代码案例搞定](#1 ~> UDP编程:三个代码案例搞定)
  • [2 ~> 先搞透基础:UDP Socket核心原理全景](#2 ~> 先搞透基础:UDP Socket核心原理全景)
    • [2.1 Socket的本质:操作系统提供的网络抽象](#2.1 Socket的本质:操作系统提供的网络抽象)
    • [2.2 核心数据结构:sockaddr\_in与地址转换](#2.2 核心数据结构:sockaddr_in与地址转换)
      • [2.2.1 地址转换函数](#2.2.1 地址转换函数)
    • [2.3 字节序:为什么必须处理大小端?](#2.3 字节序:为什么必须处理大小端?)
    • [2.4 bind函数深度解析](#2.4 bind函数深度解析)
    • [2.5 客户端为什么不需要显式bind?](#2.5 客户端为什么不需要显式bind?)
  • [3 ~> V1版本:Echo回显服务器](#3 ~> V1版本:Echo回显服务器)
    • [3.1 完整代码实现](#3.1 完整代码实现)
      • [3.1.1 Mutex\.hpp(互斥锁封装)](#3.1.1 Mutex.hpp(互斥锁封装))
      • [3.1.2 Logger\.hpp(日志模块)](#3.1.2 Logger.hpp(日志模块))
      • [3.1.3 UdpEchoServer\.hpp(核心逻辑)](#3.1.3 UdpEchoServer.hpp(核心逻辑))
      • [3.1.4 main\.cpp(程序入口)](#3.1.4 main.cpp(程序入口))
    • [3.2 编译与运行](#3.2 编译与运行)
      • [3.2.1 Makefile](#3.2.1 Makefile)
      • [3.2.2 运行命令](#3.2.2 运行命令)
      • [3.2.3 客户端测试](#3.2.3 客户端测试)
    • [3.3 V1版本的局限性](#3.3 V1版本的局限性)
  • [4 ~> V2版本:增强版英译汉字典服务器(生产级解耦)](#4 ~> V2版本:增强版英译汉字典服务器(生产级解耦))
    • [4.1 核心改进:生产级工程化能力](#4.1 核心改进:生产级工程化能力)
    • [4.2 完整代码实现](#4.2 完整代码实现)
      • [4.2.1 Mutex\.hpp(复用V1版本)](#4.2.1 Mutex.hpp(复用V1版本))
      • [4.2.2 Logger\.hpp(复用V1版本)](#4.2.2 Logger.hpp(复用V1版本))
      • [4.2.3 字典业务模块:Dictionary\.hpp](#4.2.3 字典业务模块:Dictionary.hpp)
      • [4.2.4 字典数据文件:Dict\.txt](#4.2.4 字典数据文件:Dict.txt)
      • [4.2.5 通用UDP服务器:UdpServer\.hpp](#4.2.5 通用UDP服务器:UdpServer.hpp)
      • [4.2.6 服务器主函数:Main\.cc](#4.2.6 服务器主函数:Main.cc)
    • [4.3 客户端代码(通用版)](#4.3 客户端代码(通用版))
    • [4.4 编译与运行](#4.4 编译与运行)
      • [4.4.1 Makefile](#4.4.1 Makefile)
      • [4.4.2 运行步骤](#4.4.2 运行步骤)
      • [4.5 V2版本优势总结](#4.5 V2版本优势总结)
  • [5 ~> V3版本:多人聊天室(生产级并发)](#5 ~> V3版本:多人聊天室(生产级并发))
    • [5.1 整体架构:生产者 \- 消费者模型](#5.1 整体架构:生产者 - 消费者模型)
    • [5.2 核心模块补充](#5.2 核心模块补充)
      • [5.2.1 Cond\.hpp(条件变量封装)](#5.2.1 Cond.hpp(条件变量封装))
      • [5.2.2 Thread\.hpp(线程封装)](#5.2.2 Thread.hpp(线程封装))
      • [5.2.3 InetAddr\.hpp(地址封装)](#5.2.3 InetAddr.hpp(地址封装))
      • [5.2.4 Route\.hpp(消息路由)](#5.2.4 Route.hpp(消息路由))
      • [5.2.5 UdpServer\.hpp(聊天室专用版)](#5.2.5 UdpServer.hpp(聊天室专用版))
      • [5.2.6 ThreadPool\.hpp(懒汉单例线程池)](#5.2.6 ThreadPool.hpp(懒汉单例线程池))
    • [5.3 主函数整合](#5.3 主函数整合)
    • [5.4 编译与运行](#5.4 编译与运行)
      • [5.4.1 Makefile](#5.4.1 Makefile)
      • [5.4.2 运行步骤](#5.4.2 运行步骤)
    • [5.5 客户端实现:多线程收发分离](#5.5 客户端实现:多线程收发分离)
      • [5.5.1 Linux客户端](#5.5.1 Linux客户端)
  • [6 ~> 工程化实践与踩坑总结](#6 ~> 工程化实践与踩坑总结)
    • [6.1 日志系统使用指南](#6.1 日志系统使用指南)
    • [6.2 UDP编程踩坑总结](#6.2 UDP编程踩坑总结)
      • [6.2.1 基础API踩坑](#6.2.1 基础API踩坑)
      • [6.2.2 多线程踩坑](#6.2.2 多线程踩坑)
      • [6.2.3 业务逻辑踩坑](#6.2.3 业务逻辑踩坑)
    • [6.3 客户端访问服务端,云服务器需要开放一些端口](#6.3 客户端访问服务端,云服务器需要开放一些端口)
  • [7 ~> 总结与后续优化方向](#7 ~> 总结与后续优化方向)
    • [7.1 本文总结](#7.1 本文总结)
    • [7.2 后续优化方向](#7.2 后续优化方向)
  • 附录:完整项目文件结构
  • 结尾


1 ~> UDP编程:三个代码案例搞定

很多人学UDP编程只停留在"能发能收"的Echo服务器阶段,但真实的网络服务需要解决多用户并发、业务解耦、性能瓶颈、跨平台兼容四大核心问题。本文将带你走完UDP编程的完整学习路线:

版本 功能 解决的核心问题 核心技术点
V1 Echo回显服务器 基础Socket收发、地址转换、端口绑定 socket()/bind()/recvfrom()/sendto()
V2 增强版英译汉字典服务器 网络层与业务层解耦、生产级日志、配置文件加载 回调函数、策略模式日志、文件IO
V3 多人聊天室 多用户管理、并发优化、跨平台通信 生产者-消费者模型、线程池、多线程收发分离

本文所有代码均基于C++17标准,兼容Linux与Windows平台,所有代码均来自真实生产实践,可直接编译运行,如果有问题,简单调试,通过修改参数、调整文件名、检查构造函数初始化顺序等方式可以矫正。


2 ~> 先搞透基础:UDP Socket核心原理全景

2.1 Socket的本质:操作系统提供的网络抽象

  • 定义:Socket是应用层与TCP/IP协议栈之间的编程接口,本质是一个特殊的文件描述符

  • 类比:Socket = 网络电话,IP = 手机号,端口 = 分机号

  • 核心特性 :UDP是无连接、不可靠、面向数据报的协议,支持全双工通信

2.2 核心数据结构:sockaddr_in与地址转换

cpp 复制代码
// IPv4地址结构体
struct sockaddr_in {
    short int          sin_family;  // 地址族,固定为AF_INET
    unsigned short int sin_port;    // 16位端口号(网络字节序)
    struct in_addr     sin_addr;    // 32位IP地址(网络字节序)
    unsigned char      sin_zero[8]; // 填充字节
};

2.2.1 地址转换函数

函数 功能 线程安全 推荐场景
inet\_addr() 点分十进制 → 网络序IP 简单场景
inet\_ntoa() 网络序IP → 点分十进制 单线程环境
inet\_pton() 点分十进制 → 网络序IP(支持IPv6) 多线程/生产环境
inet\_ntop() 网络序IP → 点分十进制(支持IPv6) 多线程/生产环境

坑人点inet_ntoa()使用静态缓冲区存储结果,多线程调用会互相覆盖,生产环境必须使用inet_ntop()

2.3 字节序:为什么必须处理大小端?

  • 大端序 :高位字节存低地址,网络字节序统一使用大端

  • 小端序:低位字节存低地址,x86/x86_64架构CPU默认使用小端

  • 核心转换函数

    • htons():主机序 → 网络序(短整型,用于端口)

    • ntohs():网络序 → 主机序(短整型)

    • htonl()/ntohl():长整型转换(用于IP地址)

2.4 bind函数深度解析

  • 作用:将Socket与指定的IP和端口绑定,告诉操作系统"这个Socket负责接收发往该地址的数据包"

  • 为什么服务器必须显式bind?:服务器端口必须是固定且众所周知的,客户端才能连接

  • 为什么推荐绑定 0.0.0.0

    • 0.0.0.0表示监听本机所有网卡的所有IP地址

    • 云服务器网卡上没有配置公网IP(公网IP是运营商NAT映射的),直接绑定公网IP会失败

  • 常见bind失败原因:端口被占用、权限不足(1024以下端口需要root)、绑定不存在的IP

2.5 客户端为什么不需要显式bind?

  • 客户端调用sendto()时,操作系统会自动为其分配一个随机的未被占用的端口

  • 客户端数量众多,不需要固定端口,自动分配可以避免端口冲突


3 ~> V1版本:Echo回显服务器

3.1 完整代码实现

3.1.1 Mutex.hpp(互斥锁封装)

cpp 复制代码
#ifndef __MUTEX_HPP
#define __MUTEX_HPP

#include <pthread.h>

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

// 自动加解锁的Guard(RAII风格)
class LockGuard
{
public:
    LockGuard(Mutex *lockp): _lockp(lockp)
    {
        _lockp->Lock();
    }
    ~LockGuard()
    {
        _lockp->Unlock();
    }
private:
    Mutex *_lockp;
};

#endif

3.1.2 Logger.hpp(日志模块)

cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <unistd.h>
#include <cstdlib>
#include "Mutex.hpp"

namespace LogModule
{
    std::string GetTimeStamp()
    {
        time_t timestamp = time(nullptr);
        struct tm data_time;
        localtime_r(&timestamp, &data_time);

        char data_time_str[128];
        snprintf(data_time_str, sizeof(data_time_str), "%4d-%02d-%02d %02d:%02d:%02d",
                 data_time.tm_year + 1900,
                 data_time.tm_mon + 1,
                 data_time.tm_mday,
                 data_time.tm_hour,
                 data_time.tm_min,
                 data_time.tm_sec);

        return data_time_str;
    }

    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LogLevel2String(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 "UNKNOWN";
        }
    }

    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &logmessage) = 0;
    };

    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &logmessage) override
        {
            LockGuard lockguard(&_mutex);
            std::cout << logmessage << std::endl;
        }

    private:
        Mutex _mutex;
    };

    static const std::string glogdir = "./log/";
    static const std::string glogfilename = "log.txt";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &dir = glogdir, const std::string &filename = glogfilename)
            : _logdir(dir), _logfilename(filename)
        {
            LockGuard lockguard(&_mutex);
            if (!std::filesystem::exists(_logdir))
            {
                try
                {
                    std::filesystem::create_directories(_logdir);
                }
                catch (const std::filesystem::filesystem_error &e)
                {
                    std::cerr << "创建日志目录失败: " << e.what() << '\n';
                }
            }
        }

        void SyncLog(const std::string &logmessage) override
        {
            LockGuard lockguard(&_mutex);
            std::string target = _logdir + _logfilename;
            std::ofstream out(target, std::ios::app);
            if (!out.is_open())
            {
                std::cerr << "打开日志文件失败: " << target << std::endl;
                return;
            }
            out << logmessage << "\n";
            out.close();
        }

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

    class Logger
    {
    public:
        Logger()
        {
            UseConsoleLogStrategy();
        }

        void UseConsoleLogStrategy()
        {
            _strategy = std::make_unique<ConsoleLogStrategy>();
        }

        void UseFileLogStrategy()
        {
            _strategy = std::make_unique<FileLogStrategy>();
        }

        class LogMessage
        {
        public:
            LogMessage(LogLevel level, std::string filename, int line, Logger &self)
                : _level(level),
                  _curr_time(GetTimeStamp()),
                  _pid(getpid()),
                  _filename(std::move(filename)),
                  _line(line),
                  _logger(self)
            {
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LogLevel2String(_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()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
                if (_level == LogLevel::FATAL)
                {
                    exit(EXIT_FAILURE);
                }
            }

        private:
            LogLevel _level;
            std::string _curr_time;
            pid_t _pid;
            std::string _filename;
            int _line;
            std::string _loginfo;
            Logger &_logger;
        };

        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, std::move(filename), line, *this);
        }

    private:
        std::unique_ptr<LogStrategy> _strategy;
    };

    Logger logger;

    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define ENABLE_CONSOLE_LOG() logger.UseConsoleLogStrategy()
    #define ENABLE_FILE_LOG()    logger.UseFileLogStrategy()
}

#endif

3.1.3 UdpEchoServer.hpp(核心逻辑)

cpp 复制代码
#ifndef __UDP_ECHOSERVER_HPP
#define __UDP_ECHOSERVER_HPP

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

using namespace LogModule;

class UdpEchoServer
{
public:
    UdpEchoServer(uint16_t port)
        : _sockfd(-1), _port(port)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建Socket失败!";
            exit(EXIT_FAILURE);
        }
        LOG(LogLevel::INFO) << "创建Socket成功,fd: " << _sockfd;

        struct sockaddr_in local;
        std::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)
        {
            LOG(LogLevel::FATAL) << "绑定端口[" << _port << "]失败!";
            exit(EXIT_FAILURE);
        }
        LOG(LogLevel::INFO) << "绑定端口[" << _port << "]成功!";
    }

    void Start()
    {
        LOG(LogLevel::INFO) << "UDP Echo Server 启动成功,监听端口: " << _port;
        
        char buffer[1024] = {0};
        struct sockaddr_in peer;
        socklen_t peer_len = sizeof(peer);

        while (true)
        {
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, 
                                (struct sockaddr *)&peer, &peer_len);
            if (n < 0)
            {
                LOG(LogLevel::WARNING) << "接收数据失败!";
                continue;
            }

            buffer[n] = '\0';
            std::string client_ip = inet_ntoa(peer.sin_addr);
            uint16_t client_port = ntohs(peer.sin_port);

            LOG(LogLevel::INFO) << "收到客户端[" << client_ip << ":" << client_port 
                                << "]消息: " << buffer;

            std::string echo_msg = "[Echo] " + std::string(buffer);

            sendto(_sockfd, echo_msg.c_str(), echo_msg.size(), 0,
                  (struct sockaddr *)&peer, peer_len);
        }
    }

    ~UdpEchoServer()
    {
        if (_sockfd >= 0)
        {
            close(_sockfd);
            LOG(LogLevel::INFO) << "关闭Socket,fd: " << _sockfd;
        }
    }

private:
    int _sockfd;
    uint16_t _port;
};

#endif

3.1.4 main.cpp(程序入口)

cpp 复制代码
#include "UdpEchoServer.hpp"
#include <iostream>

void Usage(const char *progname)
{
    std::cout << "Usage: " << progname << " <port>" << std::endl;
    std::cout << "Example: " << progname << " 8080" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return EXIT_FAILURE;
    }

    uint16_t port = atoi(argv[1]);
    if (port <= 1024 || port > 65535)
    {
        std::cerr << "端口必须在 1025~65535 之间!" << std::endl;
        return EXIT_FAILURE;
    }

    // ENABLE_FILE_LOG(); // 可选:切换到文件日志

    UdpEchoServer server(port);
    server.Init();
    server.Start();

    return EXIT_SUCCESS;
}

3.2 编译与运行

3.2.1 Makefile

Makefile 复制代码
CC = g++
STD = -std=c++17
CFLAGS = -Wall -g -lpthread

all: udp_echo_server

udp_echo_server: main.cpp
        $(CC) -o $@ $^ $(STD) $(CFLAGS)

.PHONY: clean
clean:
        rm -f udp_echo_server
        rm -rf log/

3.2.2 运行命令

Bash 复制代码
make
./udp_echo_server 8080

3.2.3 客户端测试

Bash 复制代码
nc -u 127.0.0.1 8080
# 输入任意字符,会收到服务器回显

3.3 V1版本的局限性

  • 网络层与业务逻辑强耦合,无法复用

  • 只能处理单客户端,不支持多用户

  • 基础日志,无生产级错误处理

  • 单线程模型,性能瓶颈明显


4 ~> V2版本:增强版英译汉字典服务器(生产级解耦)

4.1 核心改进:生产级工程化能力

相比基础版字典服务器,本版本增加了:

  • 策略模式日志系统:支持控制台/文件双日志输出,线程安全

  • 配置文件加载:从外部txt文件加载字典数据,支持动态扩展

  • 完善的错误处理:文件打开失败、格式错误等异常处理

  • 增强字典内容:每个单词包含中文翻译+英文例句+中文例句

4.2 完整代码实现

4.2.1 Mutex.hpp(复用V1版本)

代码同V1版本,此处省略。

4.2.2 Logger.hpp(复用V1版本)

代码同V1版本,此处省略。

4.2.3 字典业务模块:Dictionary.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Logger.hpp"

std::string defaultdict = "./Dict.txt";
std::string gsep = ": ";

using namespace LogModule;

class Dictionary
{
private:
    void LoadConf()
    {
        std::ifstream in(_dictfilename);
        if (!in.is_open())
        {
            LOG(LogLevel::FATAL) << "Open dictionary file error: " << _dictfilename;
            exit(1);
        }

        std::string line;
        int line_num = 0;
        while (std::getline(in, line))
        {
            line_num++;
            if (line.empty()) continue;

            auto pos = line.find(gsep);
            if (pos == std::string::npos)
            {
                LOG(LogLevel::WARNING) << "Line " << line_num << " format error, skip: " << line;
                continue;
            }

            std::string key = line.substr(0, pos);
            std::string value = line.substr(pos + gsep.size());
            _dictmap.insert({key, value});
        }

        in.close();
        LOG(LogLevel::INFO) << "Load dictionary success, total " << _dictmap.size() << " words";
    }

public:
    Dictionary(const std::string &dictfilename = defaultdict)
        : _dictfilename(dictfilename)
    {
        LoadConf();
    }

    std::string Translate(const std::string &word)
    {
        auto iter = _dictmap.find(word);
        if(iter == _dictmap.end())
        {
            return "未知单词";
        }
        return iter->second;
    }

    ~Dictionary() {}

private:
    std::string _dictfilename;
    std::unordered_map<std::string, std::string> _dictmap;
};

4.2.4 字典数据文件:Dict.txt

Plaintext 复制代码
apple: 苹果 - I eat an apple every day. / 我每天吃一个苹果。
banana: 香蕉 - The monkey is eating a banana. / 猴子正在吃香蕉。
cat: 猫 - My cat likes to sleep on the sofa. / 我的猫喜欢在沙发上睡觉。
dog: 狗 - She takes her dog for a walk every morning. / 她每天早上带她的狗去散步。
book: 书 - This book is very interesting. / 这本书非常有趣。
pen: 笔 - May I use your pen? / 我可以用一下你的笔吗?
happy: 快乐的 - She looks very happy today. / 她今天看起来很快乐。
sad: 悲伤的 - He felt sad when he lost his watch. / 他丢了手表时感到很悲伤。
run: 跑 - I run fast in the park. / 我在公园里跑得很快。
jump: 跳 - The child can jump high. / 这个孩子能跳得很高。
teacher: 老师 - Our teacher is very kind. / 我们的老师非常和蔼。
student: 学生 - He is a hardworking student. / 他是一个勤奋的学生。
car: 汽车 - His car is very fast. / 他的汽车非常快。
bus: 公交车 - I go to school by bus. / 我乘公交车上学。
love: 爱 - I love my family. / 我爱我的家人。
hate: 恨 - I hate getting up early. / 我讨厌早起。
hello: 你好 - Hello, nice to meet you! / 你好,很高兴认识你!
goodbye: 再见 - Goodbye, see you tomorrow! / 再见,明天见!
summer: 夏天 - Summer is my favorite season. / 夏天是我最喜欢的季节。
winter: 冬天 - It is very cold in winter. / 冬天非常冷。

4.2.5 通用UDP服务器:UdpServer.hpp

cpp 复制代码
#ifndef __UDP_SERVER_HPP
#define __UDP_SERVER_HPP
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Logger.hpp"

using namespace LogModule;

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

class UdpServer
{
public:
    UdpServer(callback_t cb, uint16_t port)
        : _sockfd(-1),
          _port(port),
          _cb(cb)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "Create socket error";
            exit(1);
        }
        LOG(LogLevel::INFO) << "Create socket success, fd: " << _sockfd;

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "Bind socket error";
            exit(2);
        }
        LOG(LogLevel::INFO) << "Bind socket success, port: " << _port;
    }

    void Start()
    {
        LOG(LogLevel::INFO) << "Udp Dictionary Server start running...";
        char inbuffer[1024];
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, 
                                (struct sockaddr *)&peer, &len);
            if (n < 0)
            {
                LOG(LogLevel::WARNING) << "Recvfrom error, errno: " << errno;
                continue;
            }

            inbuffer[n] = '\0';
            std::string clientip = inet_ntoa(peer.sin_addr);
            uint16_t clientport = ntohs(peer.sin_port);
            LOG(LogLevel::INFO) << "Received from [" << clientip << ":" << clientport 
                               << "]: " << inbuffer;

            std::string result;
            if(_cb)
            {
                result = _cb(inbuffer);
            }

            sendto(_sockfd, result.c_str(), result.size(), 0, 
                  (struct sockaddr *)&peer, len);
            LOG(LogLevel::DEBUG) << "Sent response to [" << clientip << ":" << clientport 
                                << "]: " << result;
        }
    }

    ~UdpServer() 
    {
        if (_sockfd >= 0)
        {
            close(_sockfd);
        }
    }

private:
    int _sockfd;
    uint16_t _port;
    callback_t _cb;
};

#endif

4.2.6 服务器主函数:Main.cc

cpp 复制代码
#include "UdpServer.hpp"
#include "Dictionary.hpp"
#include <memory>

void Usage(const std::string &proc)
{
    std::cout << "Usage: \n\t" << proc << " local_port\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    ENABLE_CONSOLE_LOG_STRATEGY();
    // ENABLE_FILE_LOG_STRATEGY();

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

    std::unique_ptr<Dictionary> dict = std::make_unique<Dictionary>();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(
        [&dict](const std::string &word) -> std::string {
            return dict->Translate(word);
        },
        port
    );

    usvr->Init();
    usvr->Start();

    return 0;
}

4.3 客户端代码(通用版)

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(const std::string &process)
{
    std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "Create socket error: " << strerror(errno) << std::endl;
        return 2;
    }

    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());

    std::cout << "=== UDP Dictionary Client ===" << std::endl;
    std::cout << "Enter 'quit' to exit" << std::endl;

    std::string word;
    while (true)
    {
        std::cout << "\nPlease enter a word: ";
        std::getline(std::cin, word);

        if (word == "quit" || word == "exit")
        {
            std::cout << "Goodbye!" << std::endl;
            break;
        }

        if (word.empty()) continue;

        sendto(sock, word.c_str(), word.size(), 0, 
              (struct sockaddr*)&server, sizeof(server));

        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, 
                            (struct sockaddr*)&temp, &len);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << "Result: " << buffer << std::endl;
        }
        else
        {
            std::cerr << "Recvfrom error" << std::endl;
        }
    }

    close(sock);
    return 0;
}

4.4 编译与运行

4.4.1 Makefile

Makefile 复制代码
CC = g++
STD = -std=c++17
CFLAGS = -Wall -g -lpthread -lstdc++fs

all: dict_server dict_client

dict_server: Main.cc
        $(CC) -o $@ $^ $(STD) $(CFLAGS)

dict_client: DictClient.cc
        $(CC) -o $@ $^ $(STD) $(CFLAGS)

.PHONY: clean
clean:
        rm -f dict_server dict_client
        rm -rf log/

4.4.2 运行步骤

  1. 确保所有文件在同一目录下

  2. 执行make编译

  3. 运行服务器:\./dict\_server 8888

  4. 运行客户端:\./dict\_client 127\.0\.0\.1 8888

  5. 输入单词查询翻译,输入quit退出

4.5 V2版本优势总结

  • 完全解耦:网络层与业务层完全分离,UdpServer可复用

  • 生产级日志:支持控制台/文件双输出,线程安全

  • 可扩展性强:字典数据从外部文件加载,无需修改代码即可扩展

  • 完善的错误处理:文件错误、网络错误、格式错误均有处理

  • 线程安全:所有共享资源访问均加锁保护


5 ~> V3版本:多人聊天室(生产级并发)

5.1 整体架构:生产者 - 消费者模型

我们采用经典的生产者-消费者分层架构,解决单线程性能瓶颈:

  • 生产者层:UdpServer主线程,专门负责接收客户端UDP数据包(IO密集型,阻塞在recvfrom)

  • 缓冲层:线程池内部的线程安全任务队列,解耦生产和消费速度,削峰填谷

  • 消费者层:线程池中的多个工作线程,负责用户管理、消息路由和广播(CPU密集型)

5.2 核心模块补充

5.2.1 Cond.hpp(条件变量封装)

cpp 复制代码
#ifndef __COND_HPP
#define __COND_HPP
#include <pthread.h>
#include "Mutex.hpp"

class Cond
{
public:
    Cond()
    {
        pthread_cond_init(&_cond, nullptr);
    }

    void Wait(Mutex &mutex)
    {
        pthread_cond_wait(&_cond, mutex.Orgin());
    }

    void NotifyOne()
    {
        pthread_cond_signal(&_cond);
    }

    void NotifyAll()
    {
        pthread_cond_broadcast(&_cond);
    }

    ~Cond()
    {
        pthread_cond_destroy(&_cond);
    }

private:
    pthread_cond_t _cond;
};

#endif

5.2.2 Thread.hpp(线程封装)

cpp 复制代码
#ifndef __THREAD_HPP
#define __THREAD_HPP
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "Logger.hpp"

using namespace LogModule;

using func_t = std::function<void()>;

enum class TSTATUS
{
    THREAD_NEW,
    THREAD_RUNNING,
    THREAD_STOP
};

static int gcnt = 1;

class Thread
{
private:
    void getprocessid()
    {
        _pid = getpid();
    }

    void getlwp()
    {
        _lwpid = syscall(SYS_gettid);
    }

    static void *routine(void *args)
    {
        Thread *ts = static_cast<Thread *>(args);
        ts->getprocessid();
        ts->getlwp();
        pthread_setname_np(pthread_self(), ts->Name().c_str());
        ts->_func();
        return nullptr;
    }

public:
    Thread(func_t f) : _joinable(true), _status(TSTATUS::THREAD_NEW), _func(f)
    {
        _name = "Worker-" + std::to_string(gcnt++);
    }

    void start()
    {
        if (_status == TSTATUS::THREAD_RUNNING)
        {
            LOG(LogLevel::INFO) << "thread is already running";
            return;
        }
        int n = pthread_create(&_tid, nullptr, routine, this);
        (void)n;
        _status = TSTATUS::THREAD_RUNNING;
    }

    void stop()
    {
        if (_status == TSTATUS::THREAD_RUNNING)
        {
            int n = pthread_cancel(_tid);
            (void)n;
            _status = TSTATUS::THREAD_STOP;
        }
        else
        {
            LOG(LogLevel::WARNING) << "thread status is : THREAD_NEW or THREAD_STOP! stop error";
        }
    }

    void join()
    {
        if (_joinable)
        {
            int n = pthread_join(_tid, nullptr);
            (void)n;
            LOG(LogLevel::INFO) << "lwp : " << _lwpid << ", name: " << _name << ", join success";
        }
        else
        {
            LOG(LogLevel::WARNING) << "lwp : " << _lwpid << ", name: " << _name << ", join failed, because thread is detach";
        }
    }

    void detach()
    {
        if (_joinable && _status == TSTATUS::THREAD_RUNNING)
        {
            _joinable = false;
            int n = pthread_detach(_tid);
            (void)n;
        }
    }

    std::string Name()
    {
        return _name;
    }

    ~Thread() {}

private:
    pthread_t _tid;
    pid_t _pid;
    pid_t _lwpid;
    std::string _name;
    bool _joinable;
    TSTATUS _status;
    func_t _func;
};

#endif

5.2.3 InetAddr.hpp(地址封装)

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr) : _net_addr(addr)
    {
        _port = ntohs(_net_addr.sin_port);
        _ip = inet_ntoa(_net_addr.sin_addr);
    }

    InetAddr(uint16_t port, std::string ip = "0.0.0.0")
        : _port(port), _ip(ip)
    {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
    }

    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }

    struct sockaddr *Addr()
    {
        return CONV(&_net_addr);
    }

    bool operator==(const InetAddr &addr)
    {
        return (_ip == addr._ip) && (_port == addr._port);
    }

    socklen_t AddrLen()
    {
        return sizeof(_net_addr);
    }

    std::string StringAddress()
    {
        return "[" + _ip + ":" + std::to_string(_port) + "]";
    }

    ~InetAddr()
    {
    }

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

5.2.4 Route.hpp(消息路由)

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include "InetAddr.hpp"
#include "Mutex.hpp"
#include "Logger.hpp"

using namespace LogModule;

class Route
{
private:
    bool IsOnline(const InetAddr &who)
    {
        for (auto &user : _users)
        {
            if (user == who)
                return true;
        }
        return false;
    }

    void AddUser(const InetAddr &who)
    {
        _users.push_back(who);
        LOG(LogLevel::INFO) << "New user online: " << who.StringAddress();
    }

    void DeleteUser(const InetAddr &who)
    {
        auto iter = std::remove_if(_users.begin(), _users.end(),
                                   [&who](const InetAddr &user)
                                   { return user == who; });
        if (iter != _users.end())
        {
            _users.erase(iter, _users.end());
            LOG(LogLevel::INFO) << "User offline: " << who.StringAddress();
        }
    }

public:
    Route() {}

    void RouteMessage(std::string message, InetAddr who, int sockfd)
    {
        LOG(LogLevel::DEBUG) << "3. RouteMessage";
        std::vector<InetAddr> temp_users;

        {
            LockGuard lockguard(&_lock);
            if (!IsOnline(who))
            {
                AddUser(who);
            }
            temp_users = _users;
        };

        std::string send_message = who.StringAddress() + "# " + message;
        for (auto &user : temp_users)
        {
            ssize_t ret = sendto(sockfd, send_message.c_str(), send_message.size(), 0, user.Addr(), user.AddrLen());
            if (ret < 0)
            {
                LOG(LogLevel::WARNING) << "sendto error: " << user.StringAddress();
            }
        }

        if (message == "QUIT")
        {
            LockGuard lockguard(&_lock);
            DeleteUser(who);
        }
    }

    ~Route() {}

private:
    std::vector<InetAddr> _users;
    Mutex _lock;
};

5.2.5 UdpServer.hpp(聊天室专用版)

cpp 复制代码
#ifndef __UDP_SERVER_HPP
#define __UDP_SERVER_HPP
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "InetAddr.hpp"
#include "Logger.hpp"

using namespace LogModule;

using callback_t = std::function<void(std::string message, InetAddr who, int sockfd)>;

class UdpServer
{
public:
    UdpServer(callback_t cb, uint16_t port)
        : _sockfd(-1),
          _port(port),
          _cb(cb)
    {
    }

    void Init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket error";
            exit(1);
        }
        LOG(LogLevel::INFO) << "create socket fd success: " << _sockfd;

        InetAddr local(_port);
        int n = bind(_sockfd, local.Addr(), local.AddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind socket error";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind socket fd success: " << _sockfd;
    }

    void Start()
    {
        char inbuffer[1024];
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n < 0)
            {
                LOG(LogLevel::WARNING) << "recvfrom error";
                break;
            }
            inbuffer[n] = 0;
            InetAddr clientaddr(peer);
            if (_cb)
            {
                _cb(inbuffer, clientaddr, _sockfd);
            }
        }
    }

    ~UdpServer()
    {
        if (_sockfd >= 0)
        {
            close(_sockfd);
            _sockfd = -1;
        }
    }

private:
    int _sockfd;
    uint16_t _port;
    callback_t _cb;
};

#endif

5.2.6 ThreadPool.hpp(懒汉单例线程池)

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include "Thread.hpp"
#include "Logger.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"

static const int gnum = 5;

using namespace LogModule;

template <typename T>
class ThreadPool
{
private:
    bool IsTaskQueueEmpty()
    {
        return _queue.empty();
    }

    T PopHelper()
    {
        T t = _queue.front();
        _queue.pop();
        return t;
    }

    void ThreadRoutine()
    {
        char name[64];
        pthread_getname_np(pthread_self(), name, sizeof(name));
        while (true)
        {
            T task;
            {
                LockGuard lockguard(&_lock);
                while (IsTaskQueueEmpty() && _isrunning)
                {
                    _sleeper_cnt++;
                    LOG(LogLevel::DEBUG) << "没有任务, 线程休眠: |" << name << "|";
                    _cond.Wait(_lock);
                    LOG(LogLevel::DEBUG) << "有任务, 线程唤醒: |" << name << "|";
                    _sleeper_cnt--;
                }
                if (IsTaskQueueEmpty() && !_isrunning)
                {
                    LOG(LogLevel::INFO) << "Thread: " << name << " quit";
                    break;
                }
                task = PopHelper();
            }
            task();
        }
    }

    ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleeper_cnt(0)
    {
        for (int i = 0; i < _num; i++)
        {
            _threads.emplace_back([this]()
            { 
            	this->ThreadRoutine(); 
            });
        }
    }

    ThreadPool(const ThreadPool<T> &) = delete;
    ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *GetInstance()
    {
        if (_instance == nullptr)
        {
            LockGuard lockguard(&_singleton_lock);
            if (_instance == nullptr)
            {
                LOG(LogLevel::DEBUG) << "首次使用,创建线程池对象";
                _instance = new ThreadPool<T>();
                _instance->Start();
            }
        }
        return _instance;
    }

    void Start()
    {
        LockGuard lockguard(&_lock);
        if (_isrunning)
            return;
        _isrunning = true;
        for (auto &thread : _threads)
            thread.start();
    }

    void Enqueue(T task)
    {
        LockGuard lockguard(&_lock);
        if (!_isrunning)
            return;
        _queue.push(task);
        if (_sleeper_cnt > 0)
            _cond.NotifyOne();
        LOG(LogLevel::DEBUG) << "2. Enqueue task";
    }

    void Stop()
    {
        LockGuard lockguard(&_lock);
        if (_isrunning)
        {
            LOG(LogLevel::DEBUG) << "关闭线程池";
            _isrunning = false;
            if (_sleeper_cnt > 0)
                _cond.NotifyAll();
        }
    }

    void Wait()
    {
        for (auto &thread : _threads)
            thread.join();
    }

    ~ThreadPool() {}

private:
    std::vector<Thread> _threads;
    int _num;
    bool _isrunning;
    int _sleeper_cnt;
    std::queue<T> _queue;
    Mutex _lock;
    Cond _cond;
    static ThreadPool<T> *_instance;
    static Mutex _singleton_lock;
};

template <typename T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;

template <typename T>
Mutex ThreadPool<T>::_singleton_lock;

5.3 主函数整合

cpp 复制代码
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include "UdpServer.hpp"
#include "Route.hpp"
#include "ThreadPool.hpp"
#include "Logger.hpp"

using namespace LogModule;

static Route g_route;

void StopHandler(int signo)
{
    LOG(LogLevel::INFO) << "recv signal: " << signo << ", stop chatroom...";
    
    ThreadPool<std::function<void()>>::GetInstance()->Stop();
    ThreadPool<std::function<void()>>::GetInstance()->Wait();
    
    LOG(LogLevel::INFO) << "chatroom stop success!";
    exit(0);
}

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

    ENABLE_CONSOLE_LOG_STRATEGY();
    // ENABLE_FILE_LOG_STRATEGY();

    signal(SIGINT, StopHandler);
    signal(SIGTERM, StopHandler);

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

    auto *tp = ThreadPool<std::function<void()>>::GetInstance();

    UdpServer server
    (
        [&g_route, tp](std::string message, InetAddr who, int sockfd) 
        {
            LOG(LogLevel::INFO) << "1. get a message: " << message 
                               << ", client addr: " << who.Ip() << ":" << who.Port();
            auto task = [message, who, sockfd, &g_route]() 
            {
                g_route.RouteMessage(message, who, sockfd);
            };
            tp->Enqueue(task);
        },
        port
    );
    
    server.Init();

    LOG(LogLevel::INFO) << "chatroom start success! port: " << port;

    server.Start();

    return 0;
}

5.4 编译与运行

5.4.1 Makefile

Bash 复制代码
CC = g++
STD = -std=c++17
CFLAGS = -Wall -g -lpthread -lstdc++fs

all: chat_server chat_client

chat_server: ChatMain.cc
        $(CC) -o $@ $^ $(STD) $(CFLAGS)

chat_client: ChatClient.cc
        $(CC) -o $@ $^ $(STD) $(CFLAGS)

.PHONY: clean
clean:
        rm -f chat_server chat_client
        rm -rf log/

5.4.2 运行步骤

Bash 复制代码
make
./chat_server 8888
nc -u 127.0.0.1 8888

5.5 客户端实现:多线程收发分离

5.5.1 Linux客户端

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>
#include <cstring>
#include "InetAddr.hpp"
#include <sys/socket.h>
#include <unistd.h>

int sockfd = -1;
std::string server_ip;
uint16_t server_port = 0;

void Usage(const std::string& name) 
{
    std::cerr << "Usage: " << name << " server_ip server_port" << std::endl;
}

void RecvMessage() 
{
    char buffer[4096];
    while (true) 
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, 
                            (struct sockaddr*)&temp, &len);
        if (n > 0) 
        {
            buffer[n] = '\0';
            std::cerr << buffer << std::endl;
        }
    }
}

void SendMessage() 
{
    InetAddr server_addr(server_port, server_ip);
    std::string line;
    while (true) 
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);
        if (line.empty()) continue;
        sendto(sockfd, line.c_str(), line.size(), 0, 
               server_addr.Addr(), server_addr.AddrLen());
        
        if (line == "QUIT") 
        {
            std::cout << "Goodbye!" << std::endl;
            close(sockfd);
            exit(0);
        }
    }
}

int main(int argc, char* argv[]) 
{
    if (argc != 3) 
    {
        Usage(argv[0]);
        exit(0);
    }
    server_ip = argv[1];
    server_port = std::stoi(argv[2]);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) 
    {
        std::cerr << "Create socket error" << std::endl;
        exit(1);
    }

    std::thread recv_thread(RecvMessage);
    std::thread send_thread(SendMessage);

    recv_thread.join();
    send_thread.join();

    close(sockfd);
    return 0;
}

6 ~> 工程化实践与踩坑总结

6.1 日志系统使用指南

  • 默认控制台日志:程序启动时自动启用

  • 切换到文件日志 :在main函数开头调用ENABLE\_FILE\_LOG\_STRATEGY\(\)

  • 日志等级:从低到高为DEBUG &lt; INFO &lt; WARNING &lt; ERROR &lt; FATAL

  • 日志格式\[时间\] \[等级\] \[进程ID\] \[文件名\] \[行号\] \- 日志内容

6.2 UDP编程踩坑总结

6.2.1 基础API踩坑

  1. bind公网IP失败 :云服务器网卡上没有配置公网IP,只能绑定0.0.0.0

  2. 端口被占用 :使用netstat -tulpn | grep 端口号查看占用进程

  3. recvfrom返回-1:检查sockfd是否有效、len参数是否正确初始化

  4. sendto返回-1:检查目标地址是否正确、防火墙是否放行

6.2.2 多线程踩坑

  1. 线程池未启动 :懒汉单例线程池必须在GetInstance()中启动线程

  2. vector迭代器失效:多线程遍历vector时必须加锁,避免同时扩容

  3. inet_ntoa线程不安全 :多线程环境下必须使用inet\_ntop

6.2.3 业务逻辑踩坑

  1. 用户身份标识错误 :必须用 IP\+端口 唯一标识用户,不能只用IP

  2. 锁粒度过大:加锁期间只操作共享资源,转发逻辑放在锁外

  3. 中文乱码:Linux默认UTF-8,Windows默认GBK,需要统一编码

6.3 客户端访问服务端,云服务器需要开放一些端口

为保障云服务器安全,厂商已通过防火墙对所有端口进行了限制处理,导致客户端无法正常访问服务器端口。若需解决该问题,可在防火墙或安全组中开放云服务器的端口(建议开放8000~9000端口范围),相关操作有对应视频教程,完成端口开放后,客户端即可正常访问服务器。


7 ~> 总结与后续优化方向

7.1 本文总结

我们完成了UDP编程从入门到生产级的完整演进:

  • 从V1的Echo服务器掌握了基础Socket收发

  • 从V2的增强版字典服务器学会了生产级工程化实践(日志、配置、解耦)

  • 从V3的聊天室掌握了多用户管理、并发优化和跨平台通信

7.2 后续优化方向

  1. 用户下线检测:实现心跳机制,定期清理超时用户

  2. 消息可靠性:实现ACK确认和重传机制

  3. 大消息传输:实现消息分片与重组

  4. 用户体系:实现登录注册,支持用户名密码

  5. 私聊功能:支持一对一消息发送

  6. 性能优化:使用无锁队列、epoll非阻塞IO


附录:完整项目文件结构

Plaintext 复制代码
udp_project/
├── V1-Echo/
│   ├── Mutex.hpp
│   ├── Logger.hpp
│   ├── UdpEchoServer.hpp
│   ├── main.cpp
│   └── Makefile
├── V2-Dictionary/
│   ├── Mutex.hpp
│   ├── Logger.hpp
│   ├── Dictionary.hpp
│   ├── UdpServer.hpp
│   ├── Main.cc
│   ├── DictClient.cc
│   ├── Dict.txt
│   └── Makefile
├── V3-ChatRoom/
│   ├── Mutex.hpp
│   ├── Logger.hpp
│   ├── Cond.hpp
│   ├── Thread.hpp
│   ├── InetAddr.hpp
│   ├── Route.hpp
│   ├── UdpServer.hpp
│   ├── ThreadPool.hpp
│   ├── ChatMain.cc
│   ├── ChatClient.cc
│   └── Makefile
└── README.md

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【Linux网络】Linux 网络编程入门:UDP Socket 编程(上)

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
qq_4523962316 小时前
第十五篇:《UI自动化中的稳定性优化:解决flaky tests的七种武器》
运维·ui·自动化
j_xxx404_17 小时前
Linux:静态链接与动态链接深度解析
linux·运维·服务器·c++·人工智能
Johnstons17 小时前
Wireshark ExpertInfo是什么?一文讲透异常分级、适用场景、和传统抓包阅读的区别与排查标准
网络·测试工具·wireshark·es
alxraves17 小时前
医疗器械软件注册指导原则注意事项
网络·安全·健康医疗·制造
_只道当时是寻常17 小时前
【Codex】Ubuntu 安装 Codex CLI 并解决 Clash 代理与账号认证问题
linux·ubuntu·chatgpt
墨风如雪18 小时前
别被“高价建站”劝退了!我跑了多年的 WordPress 架构,一年只花 $25.7
服务器
Elastic 中国社区官方博客18 小时前
Elastic-caveman : 在不损失 Elastic 最佳效果的情况下,将 AI 响应 tokens 减少64%
大数据·运维·数据库·人工智能·elasticsearch·搜索引擎·全文检索
brucelee18618 小时前
Claude Code 安装教程(Windows / Linux / macOS)
linux·windows·macos
云飞云共享云桌面18 小时前
东莞智能装备工厂数字化实践—研发部门10名SolidWorks设计共享一台云主机流畅设计
服务器·自动化·汽车·负载均衡·制造