【Linux网络编程】2. Socket编程 UDP

文章目录

一、函数介绍

1、字符串IP -> 网络序IP

1)inet_aton 函数

2)inet_addr 函数

3)inet_pton 函数

2、网络序IP → 字符串IP

1)inet_ntoa 函数

2)inet_ntop 函数

3、UDP 收发数据

1)sendto 函数

2)recvfrom 函数

二、EchoServer(回显服务)

说明: 简单的回显服务器和客户端代码

1、单进程

代码结构:

c++ 复制代码
Mutex.hpp
Log.hpp
InetAddr.hpp
UdpServer.hpp
UdpServer.cc
UdpClient.cc

1)Mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    // 互斥锁封装类
    class Mutex
    {
    public:
        // 初始化锁
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        // 加锁
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
        }

        // 解锁
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
        }

        // 获取锁指针
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }

        // 销毁锁
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex; // 原生互斥锁对象
    };

    // RAII 自动锁
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }

        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}
```### 1)Mutex.hpp
```cpp
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    // 互斥锁封装类
    class Mutex
    {
    public:
        // 初始化锁
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        // 加锁
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
        }

        // 解锁
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
        }

        // 获取锁指针
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }

        // 销毁锁
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex; // 原生互斥锁对象
    };

    // RAII 自动锁
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }

        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}

2)Log.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;

    const std::string gsep = "\r\n";

    // 日志策略基类(接口)
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 控制台日志输出(线程安全)
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log/"; //   /var/log
    const std::string defaultfile = "my.log";

    // 文件日志输出(自动建目录、线程安全)
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
                return;

            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e) 
            {
                std::cerr << e.what() << std::endl;
            }
        }

        // 追加写入日志文件
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
                return;

            out << message << gsep;
            out.close();
        }

    private:
        std::string _path;
        std::string _file;
        Mutex _mutex;
    };

    // 日志等级类
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 日志等级转字符串
    std::string LevelStr(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";
        }
    }

    // 获取格式化时间字符串(线程安全)
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm; // 出参
        localtime_r(&curr, &curr_tm);
        char buf[128]; // 出参
        snprintf(buf, sizeof(buf), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr_tm.tm_year + 1900,
                 curr_tm.tm_mon + 1,
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return buf;
    }

    // 日志核心管理类
    class Logger 
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }

        // 切换为文件输出
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        // 切换为控制台输出
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 日志消息构造: 负责拼接内容, 析构时自动输出
        class LogMessage
        {
        public:
            // 构造日志头部(时间、等级、进程ID、文件名、行号)
            LogMessage(LogLevel level, std::string src_name, int line_number, Logger &logger)
                :  _logger(logger) 
            {
                std::stringstream ss;
                ss << "[" << GetTimeStamp() << "] "
                   << "[" << LevelStr(level) << "] "
                   << "[" << getpid() << "] "
                   << "[" << src_name << "] "
                   << "[" << line_number << "] - ";
                _loginfo = ss.str(); 
            }

            // 流方式拼接日志内容
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;           
                _loginfo += ss.str(); 
                return *this;         
            }

            // 析构自动输出日志
            ~LogMessage()
            {
                if (_logger._fflush_strategy) 
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:     
            std::string _loginfo;   
            Logger &_logger;        
        };

        // 仿函数接口, 创建日志消息
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

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

    Logger logger; 

// 简化调用宏
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

3)InetAddr.hpp

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

// IPv4地址封装: 主机格式 <-> 网络格式
class InetAddr
{
public:
    // 构造: 网络地址 -> 主机格式
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _port = ntohs(_addr.sin_port); // port
        char ipbuffer[64]; // ip      
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer; 
    }

    // 构造: 指定IP+端口 -> 网络地址
    InetAddr(const std::string &ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr); // ip
        _addr.sin_port = htons(_port);                    // port
    }

    // 获取点分十进制IP
    std::string Ip() { return _ip; }
    
    // 获取主机字节序端口
    uint16_t Port() { return _port; }
    
    // 获取原生网络地址结构体
    const struct sockaddr_in &NetAddr() { return _addr; }
    
    // 转为 ip:port 格式字符串
    std::string StringAddr() { return _ip + ":" + std::to_string(_port); }

    // 比较两个地址是否相同
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }

    ~InetAddr() {}

private:
    struct sockaddr_in _addr; // 网络字节序地址
    std::string _ip;          // 点分十进制IP
    uint16_t _port;           // 主机字节序端口
};

2)UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

// 回调函数类型: 处理客户端消息
using func_t = std::function<std::string(const std::string &, InetAddr &)>;

const int defaultfd = -1;

// UDP服务器封装
class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }

    // 初始化服务器: 创建socket + 绑定端口
    void Init()
    {
        // 1. 创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket create error";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket create success: " << _sockfd;

        // 2. 绑定本机IP和端口
        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 error";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;
    }

    // 启动服务器: 循环接收 -> 处理 -> 回复
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer; // 客户端地址
            socklen_t len = sizeof(peer);

            // 1. 接收客户端消息
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                InetAddr client(peer);
                buffer[s] = 0;
                // 2. 调用回调函数处理业务
                std::string result = _func(buffer, client);
                // 3. 把处理结果发回客户端
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;     // 套接字文件描述符
    uint16_t _port;  // 服务器端口
    bool _isrunning; // 运行状态
    func_t _func;    // 业务处理回调
};

3)UdpServer.cc

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

// 默认回调: 回复 hello + 消息
std::string defaulthandler(const std::string &msg, InetAddr &)
{
    return "hello, " + msg;
}

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

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

    // 1. 创建UDP服务器, 绑定默认回调
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);

    // 2. 初始化服务器
    usvr->Init();

    // 3. 启动服务器
    usvr->Start();

    return 0;
}

4)UdpClient.cc

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

// ./udpclient 服务端IP 端口
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

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

    // 2. 填充服务端地址
    struct sockaddr_in server; // 出参
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 3. 循环: 发送消息+接收回显
    while (true)
    {
        // 输入消息
        std::string input; 
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);

        // 发送给服务端
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));

        // 接收服务端回复
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(buffer) - 1;
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }

    close(sockfd);

    return 0;
}

进行测试:
客户端

服务端

三、DictServer(翻译服务)

说明: 实现一个简单的英译汉的网络字典

代码结构:

cpp 复制代码
Mutex.hpp  
Log.hpp
InetAddr.hpp
UdpServer.hpp  
UdpServer.cc // 改动
UdpClient.cc
dictionary.txt 
Dict.hpp 

1、单进程

1)dictionary.txt

cpp 复制代码
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
hello: 
: 你好



run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

2)Dict.hpp

cpp 复制代码
#pragma once

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

const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";

using namespace LogModule;

// 翻译字典类: 加载文件+单词翻译
class Dict
{
public:
    Dict(const std::string& path=defaultdict)
    :_dict_path(path)
    {}

    // 加载字典文件
    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if(!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典失败: " << _dict_path;
            return false;
        }

        std::string line; // 出参
        while(std::getline(in,line)) // 一行一行读取
        {
            auto pos = line.find(sep);
            if(pos==std::string::npos)
            {
                LOG(LogLevel::WARNING) << "解析失败: " << line;
                continue;
            }

            std::string english = line.substr(0, pos);
            std::string chinese = line.substr(pos + sep.size());
            if(english.empty()||chinese.empty())
            {
                LOG(LogLevel::WARNING) << "无效内容: " << line;
                continue;
            }

            _dict[english] = chinese;
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }

        in.close();
        return true;
    }

    // 翻译单词
    std::string Translate(const std::string& word,InetAddr& client)
    {
        auto it = _dict.find(word);
        if(it==_dict.end())
        {
            LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->None";
            return "None";
        }
        
        LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << ":" << client.Port() << "]# " << word << "->" << it->second;
        return it->second;
    }

    ~Dict()
    {}

private:
    std::string _dict_path; // 字典文件路径
    std::unordered_map<std::string, std::string> _dict; // 英汉对照词典
};

3)UdpServer.cc

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

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

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

    // 1. 创建字典对象
    Dict d;
    
    // 2. 加载字典文件
    d.LoadDict();
    
    // 3. 创建UDP服务器, 绑定翻译回调
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
    [&d](const std::string &word, InetAddr &client)
    {
        // 收到单词->翻译并返回结果
        return d.Translate(word, client);
    });

    // 4. 初始化服务器
    usvr->Init();

    // 5. 启动服务器
    usvr->Start();

    return 0;
}

客户端

服务端

四、ChatServer(简单聊天室服务)

1、线程池

代码结构:

c++ 复制代码
Mutex.hpp
Cond.hpp 
Log.hpp
Thread.hpp  
ThreadPool.hpp 
InetAddr.hpp
UdpServer.hpp // 改动
UdpServer.cc // 改动
UdpClient.cc // 改动
Route.hpp

1)Cond.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"

using namespace MutexModule;

namespace CondModule
{
    // 条件变量封装
    class Cond
    {
    public:
        Cond()
        {
            pthread_cond_init(&_cond, nullptr);
        }

        // 等待: 阻塞并释放锁
        void Wait(Mutex &mutex)
        {
            int n = pthread_cond_wait(&_cond, mutex.Get());
        }

        // 唤醒一个等待线程
        void Signal() 
        {
            int n = pthread_cond_signal(&_cond);
        }

        // 唤醒所有等待线程
        void Broadcast() 
        {
            int n = pthread_cond_broadcast(&_cond);
        }

        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond; // 底层条件变量
    };
}

2)Thread.hpp

cpp 复制代码
#pragma once

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

namespace ThreadModule
{
    // 全局线程编号(自动命名)
    static uint32_t number = 1;

    class Thread
    {
        using func_t = std::function<void()>; // 线程执行函数类型

    private:
        // 标记线程已分离
        void EnableDetach()
        {
            _isdetach = true;
        }

        // 标记线程运行中
        void EnableRunning()
        {
            _isrunning = true;
        }

        // 线程入口(必须static)
        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            self->EnableRunning();

            // 分离线程
            if (self->_isdetach)
                self->Detach();

            // 设置线程名
            pthread_setname_np(self->_tid, self->_name.c_str());

            // 执行用户任务
            self->_func();
            return nullptr;
        }

    public:
        // 构造:传入回调,自动生成线程名
        Thread(func_t func)
            : _tid(0),
              _isdetach(false),
              _isrunning(false),
              res(nullptr),
              _func(func)
        {
            _name = "thread-" + std::to_string(number++);
        }

        // 分离线程(自动回收)
        void Detach()
        {
            if (_isdetach || !_isrunning) return;
            pthread_detach(_tid);
            _isdetach = true;
        }

        // 创建并启动线程
        bool Start()
        {
            if (_isrunning) return false;

            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n == 0)
            {
                std::cout << _name << " create success\n";
                return true;
            }
            std::cerr << "create error: " << strerror(n) << "\n";
            return false;
        }

        // 取消线程
        bool Stop()
        {
            if (!_isrunning) return false;

            int n = pthread_cancel(_tid);
            if (n == 0)
            {
                _isrunning = false;
                std::cout << _name << " stop\n";
                return true;
            }
            std::cerr << "cancel error: " << strerror(n) << "\n";
            return false;
        }

        // 等待线程结束
        void Join()
        {
            if (_isdetach)
            {
                std::cout << "线程已分离,无法join\n";
                return;
            }
            pthread_join(_tid, &res);
            std::cout << "join success\n";
        }

        // 获取线程名
        std::string Name()
        {
            return _name;
        }

        ~Thread() {}

    private:
        pthread_t _tid;      // 线程ID
        std::string _name;   // 线程名
        bool _isdetach;      // 是否分离
        bool _isrunning;     // 是否运行
        void *res;           // 线程返回值
        func_t _func;        // 线程执行函数
    };
}

3)ThreadPool.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Log.hpp"
#include "Thread.hpp"

namespace ThreadPoolModule
{
    using namespace MutexModule;
    using namespace LogModule;
    using namespace CondModule;
    using namespace ThreadModule;

    const int gnum = 5;

    template <typename T>
    class ThreadPool
    {
    private:
        // 唤醒所有休眠线程
        void WakeUpAllThread()
        {
            {
                LockGuard lockguard(_mutex);
                if (_sleepernum)
                    _cond.Broadcast();
                LOG(LogLevel::INFO) << "唤醒所有休眠线程";
            }
        }

        // 唤醒一个休眠线程
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";
        }

        // 构造: 创建指定数量的线程
        ThreadPool(int num = gnum)
            : _num(num),
              _isrunning(false),
              _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }

        // 启动线程池
        void Start()
        {
            if (_isrunning)
                return;

            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }

    public:
        // 禁用拷贝与赋值
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

        // 获取单例实例
        static ThreadPool<T> *GetInstance()
        {
            if (inc == nullptr)
            {
                LockGuard lockguard(_lock);
                LOG(LogLevel::DEBUG) << "获取单例...";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例,创建之...";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }

        // 停止线程池
        void Stop()
        {
            if (!_isrunning)
                return;

            _isrunning = false;
            WakeUpAllThread();
        }

        // 等待所有线程退出
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }

        // 线程处理任务
        void HandlerTask()
        {
            char name[128]; // 出参
            pthread_getname_np(pthread_self(), name, sizeof(name));

            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);

                    // 任务队列为空, 等待任务
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex); // 释放锁并休眠,被唤醒后重新获取锁
                        _sleepernum--;
                    }

                     // 线程退出条件
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }

                    // 取出任务
                    t = _taskq.front();
                    _taskq.pop();
                }

                // 执行任务
                t();
            }
        }

        // 任务入队
        bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                {
                    LockGuard lockguard(_mutex);
                    _taskq.push(in);
                }

                // 所有线程都在休眠, 唤醒一个
                if (_threads.size() == _sleepernum)
                    WakeUpOne();

                return true;
            }
            return false;
        }

        ~ThreadPool() = default;

    private:
        std::vector<Thread> _threads; // 线程数组
        int _num;                     // 线程数量
        std::queue<T> _taskq;         // 任务队列
        Cond _cond;                   // 条件变量
        Mutex _mutex;                 // 互斥锁

        bool _isrunning; // 运行状态
        int _sleepernum; // 休眠线程数

        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;        // 单例创建锁
    };

    // 静态成员初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;
}

4)Route.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include "Log.hpp"
#include "Mutex.hpp"
#include "InetAddr.hpp"

using namespace LogModule;
using namespace MutexModule;

// 路由模块: UDP群聊转发
class Route
{
private:
    // 判断用户是否在线
    bool IsExist(InetAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
            {
                return true;
            }
        }
        return false;
    }

    // 添加在线用户
    void AddUser(InetAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增在线用户: " << peer.StringAddr();
        _online_user.push_back(peer);
    }

    // 删除离线用户
    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                LOG(LogLevel::INFO) << "删除用户: " << peer.StringAddr();
                _online_user.erase(iter);
                break;
            }
        }
    }

public:
    Route()
    {
    }

    // 消息路由: 群发消息+上下线管理
    void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
    {
        LockGuard lockguard(_mutex);

        // 未入线则加入列表
        if (!IsExist(peer))
            AddUser(peer);

        // 构造转发消息
        std::string send_message = peer.StringAddr() + "# " + message;

        // 转发给所有在线用户
        for (auto &user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0,
                   (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }

        // 收到QUIT则下线
        if (message == "QUIT")
        {
            DeleteUser(peer);
        }
    }

    ~Route()
    {
    }

private:
    std::vector<InetAddr> _online_user; // 在线用户列表
    Mutex _mutex;                       // 互斥锁
};

5)UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>
#include <string>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

// 回调函数类型: 处理消息+客户端地址
using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;

const int defaultfd = -1;

// UDP服务器封装
class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }

    // 初始化服务器: 创建socket + 绑定端口
    void Init()
    {
        // 1. 创建UDP套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;

        // 2. 绑定本机IP和端口
        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 error";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd: " << _sockfd;
    }

    // 启动服务器: 循环接收 -> 调用回调处理
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer; // 客户端地址
            socklen_t len = sizeof(peer);

            // 1. 接收客户端消息
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                InetAddr client(peer);
                buffer[s] = 0;
                // 2. 调用回调函数处理业务
                _func(_sockfd, buffer, client);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;     // 套接字文件描述符
    uint16_t _port;  // 服务器端口
    bool _isrunning; // 运行状态
    func_t _func;    // 业务处理回调
};

6)UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "UdpServer.hpp"
#include "ThreadPool.hpp"

using namespace ThreadPoolModule;

// 任务类型
using task_t = std::function<void()>;

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

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

    // 1. 创建路由对象
    Route r;

    //  2. 获取线程池单例
    auto tp = ThreadPool<task_t>::GetInstance();

    // 3. 创建UDP服务器: 收到消息 -> 提交线程池 -> 群聊转发
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, 
        [&r, &tp](int sockfd, const std::string &message, InetAddr &peer)
    { 
        // 绑定路由处理任务
        task_t t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
        // 把任务扔进线程池
        tp->Enqueue(t); 
    });

    // 4. 初始化服务器
    usvr->Init();

    // 5. 启动服务器
    usvr->Start();

    return 0;
}

7)UdpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
using namespace ThreadModule;

// 全局变量
int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;

// 接收线程: 循环读取服务端消息并打印
void Recv()
{
    while (true)
    {
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cerr << buffer << std::endl; 
        }
    }
}

// 发送线程: 读取用户输入并发送给服务端
void Send()
{
    // 填充服务端地址
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 上线通知
    const std::string online = "inline";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));

    // 循环发送消息
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# "; 
        std::getline(std::cin, input); 

        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));

        // 退出处理
        if (input == "QUIT")
        {
            pthread_cancel(id);
            break;
        }
    }
}

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    server_ip = argv[1];
    server_port = std::stoi(argv[2]);

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

    // 2. 创建收发线程
    Thread recver(Recv);
    Thread sender(Send);

    // 3. 启动线程
    recver.Start();
    sender.Start();
    id = recver.Id();

    // 5. 等待线程结束
    recver.Join();
    sender.Join();

    return 0;
}

客户端1

客户端2

服务端

相关推荐
liulilittle1 小时前
TCP UCP v1.0:BBR 的非破坏性约束层
网络·c++·网络协议·tcp/ip·算法·c·通信
徒劳爱学仙1 小时前
全志 V821 韦东山 Avaota-F1-B ubuntu开发环境搭建
linux·运维·ubuntu
z200509301 小时前
【linux学习】linux的基本指令
linux·学习
迷枫7121 小时前
Linux 磁盘管理全攻略:从物理硬件到在线扩容
linux
皮皮学姐分享-ppx2 小时前
上市公司数字技术风险暴露数据(2010-2024)|《经济研究》同款大模型测算
大数据·网络·数据库·人工智能·chatgpt·制造
皮卡蛋炒饭.3 小时前
应用层协议HTTP
网络·网络协议·http
java_logo3 小时前
轻量AI接口网关一键部署|calciumion/new-api Windows/Linux Docker 部署全教程
linux·人工智能·windows·one api·calciumion·ai网关部署·one api 部署
原来是猿3 小时前
Linux - 【理解进程组、会话与作业控制】
linux·运维·服务器
wearegogog1233 小时前
Modbus TCP 通讯协议实现
服务器·网络·tcp/ip