【Linux】32:Socket编程2-UDP

目录

一、简单聊天室v1

[1.1 Mutex.hpp](#1.1 Mutex.hpp)

[1.2 Log.hpp](#1.2 Log.hpp)

[1.3 Thread.hpp](#1.3 Thread.hpp)

[1.4 InetAddr.hpp](#1.4 InetAddr.hpp)

[1.5 Route.hpp](#1.5 Route.hpp)

[1.6 UdpClient.cc](#1.6 UdpClient.cc)

[1.7 UdpServer.hpp](#1.7 UdpServer.hpp)

[1.8 UdpServer.cc](#1.8 UdpServer.cc)

二、简单聊天室v2

[2.1 Mutex.hpp](#2.1 Mutex.hpp)

[2.2 Log.hpp](#2.2 Log.hpp)

[2.3 Cond.hpp](#2.3 Cond.hpp)

[2.4 Thread.hpp](#2.4 Thread.hpp)

[2.5 InetAddr.hpp](#2.5 InetAddr.hpp)

[2.6 Route.hpp](#2.6 Route.hpp)

[2.7 ThreadPool.hpp](#2.7 ThreadPool.hpp)

[2.8 UdpClient.cc](#2.8 UdpClient.cc)

[2.9 UdpServer.hpp](#2.9 UdpServer.hpp)

[2.10 UdpServer.cc](#2.10 UdpServer.cc)

[三 、总结接口](#三 、总结接口)

[3.1 套接字(UDP)](#3.1 套接字(UDP))

[3.2 IP 转换](#3.2 IP 转换)

[3.3 字节序](#3.3 字节序)

[3.4 pthread 线程](#3.4 pthread 线程)

[3.5 互斥锁](#3.5 互斥锁)

[3.6 条件变量](#3.6 条件变量)


一、简单聊天室v1

1.1 Mutex.hpp

【Linux】28:封装线程,锁,条件变量,日志-CSDN博客

1.2 Log.hpp

【Linux】28:封装线程,锁,条件变量,日志-CSDN博客

1.3 Thread.hpp

【Linux】28:封装线程,锁,条件变量,日志-CSDN博客

1.4 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>
// 网络地址和主机地址转换的类

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _port = ntohs(addr.sin_port);
        _ip = inet_ntoa(addr.sin_addr);
    }
    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }
    const struct sockaddr_in &NetAddr()
    {
        return _addr;
    }
    bool operator==(const InetAddr &addr)
    {
        return _ip == addr._ip && _port == addr._port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr() {}

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

1.5 Route.hpp

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

using namespace LogModule;
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)
    {
        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, (struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }
        // 这个用户一定已经存在了
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除了一个在线用户" << peer.StringAddr();
            DeleteUser(peer);
        }
    }
    ~Route() {}

private:
    // 首次给我发消息,等同于登录
    std::vector<InetAddr> _online_user; // 在线用户
};

1.6 UdpClient.cc

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

std::string server_ip;
uint16_t server_port;
int sockfd;
pthread_t id;
void Recv()
{
    while (1)
    {
        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 = "online";
    int n = 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;
        }
    }
}
// ./updclient 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:创建套接字
    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);

    recver.Start();
    sender.Start();

    id = recver.Id();

    recver.Join();
    sender.Join();

    return 0;
}

1.7 UdpServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"

using func_t = std::function<void(int, const std::string &, InetAddr &)>;

using namespace LogModule;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          //_ip(ip),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }
    void Init()
    {
        //  1:创建套接字
        _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:绑定socket信息,主要是ip和端口.
        // 2.1填充socketaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        // 在网络通信的时候,服务器会不会将ip和端口号发送给client?
        // ip和端口号一定会发送到网络

        // ip和端口号是本地存储格式,所以需要将本地格式转换为网络序列!!
        local.sin_port = htons(_port);

        // 1:ip转成4字节  2:4字节转成网络序列  ->in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        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;

                _func(_sockfd, buffer, client);
            }
        }
    }
    ~UdpServer() {}

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip; // 用的是字符串风格,点分十进制,"192.168.1.1"
    bool _isrunning;
    func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

1.8 UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp" //网络通信的功能
#include "Route.hpp"
// ./udpserver ip 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:网络服务器对象提供通信功能
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InetAddr &client)
                                                                  { r.MessageRoute(sockfd, message, client); });
    usvr->Init();
    usvr->Start();
    return 0;
}

当客户端这样写的话,如果不发送消息,就会卡在getline这里,就收不到其他人的消息。

所以客户端需要多线程改造,让一个线程不断从标准输入获取消息,发送消息,另外一个线程不断的获取消息。
输入的消息和输出的消息混在一起了。我们可以打开两个xshell,让上面的xshell当成输出窗口,下面的当作输入窗口。如下所示:

二、简单聊天室v2

增加线程池功能,把收到的消息当成任务,放进阻塞队列中,提前创建一批线程,让这一批线程从阻塞队列里面拿取任务,执行任务。

当Udpserver收到消息之后,不应该由udpserver发送,而是由线程发送。当Udpserver收到消息之后,Udpserver应该创建一个任务,把任务push到线程池里面就可以了。

2.1 Mutex.hpp

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

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            pthread_mutex_lock(&_mutex);
        }
        void UnLock()
        {
            pthread_mutex_unlock(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex) : _mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.UnLock();
        }

    private:
        Mutex &_mutex;
    };
}

2.2 Log.hpp

cpp 复制代码
#ifndef __LOG_HPP
#define __LOG_HPP
#include <iostream>
#include <filesystem>
#include <string>
#include <memory>
#include <unistd.h>
#include <fstream>
#include <ctime>
#include <cstdio>
#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:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }

    private:
        Mutex _mutex;
    };
    // 文件打印日志的策略
    const std::string defaultpath = "./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() << "\n";
            }
        }

        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();
        }
        ~FileLogStrategy() {}

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 1:形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(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 timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%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 timebuffer;
    }
    // 1:形成日志 2根据不同的策略,完成刷新
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 内部类,表示的是未来的一条日志
        class logMessage
        {
        public:
            logMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),
                  _level(level),
                  _pid(getpid()),
                  _src_name(src_name),
                  _line_number(line_number),
                  _logger(logger)
            {
                // 日志的左半部分拼进来
                std::stringstream ss;
                ss << "[" << _curr_time << "]"
                   << "[" << Level2Str(_level) << "]"
                   << "[" << _pid << "]"
                   << "[" << _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 _curr_time;
            LogLevel _level;
            pid_t _pid;
            std::string _src_name;
            int _line_number;
            std::string _loginfo; // 合并之后一条完整的信息

            Logger &_logger;
        };

        logMessage operator()(LogLevel level, std::string name, int line)
        {
            return logMessage(level, name, line, *this);
        }
        ~Logger() {}

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

#endif

2.3 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)
        {
            pthread_cond_wait(&_cond, mutex.Get());
        }
        void Singal()
        {
            pthread_cond_signal(&_cond);
        }
        void BroadCast()
        {
            pthread_cond_broadcast(&_cond);
        }
        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }

    private:
        pthread_cond_t _cond;
    };
}

2.4 Thread.hpp

cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <functional>
namespace ThreadModule
{
    static uint32_t number = 1;
    class Thread
    {
        using func_t = std::function<void()>;
        void EnableDetach()
        {
            // std::cout << "线程被分离了" << std::endl;
            _isdetach = true;
        }

    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) // 如果线程已经分离了,就不需要再分离了。
                return;
            if (_isrunning)
                pthread_detach(_tid);
            EnableDetach();
        }

        void EnableRunning()
        {
            _isrunning = true;
        }
        static void *Routine(void *args)
        {
            Thread *self = (Thread *)args;
            self->EnableRunning();
            if (self->_isdetach) // 如果线程的分离标志是true,就调用系统调用pthread_detach来将线程设置分离
                self->Detach();
            self->_func();
            return nullptr;
        }
        bool Start()
        {
            if (_isrunning)
                return false;
            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                // std::cerr << "create pthread error" << strerror(n) << std::endl;
                return false;
            }
            else
            {
                // std::cout << _name << " create sucess" << std::endl;
            }
            return true;
        }
        bool Stop()
        {
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    // std::cerr << "cancel pthread error" << strerror(n) << std::endl;
                    return false;
                }
                else
                {
                    _isrunning = false;
                    std::cerr << _name << " stop" << std::endl;
                }
            }
            return true;
        }
        bool Join()
        {
            if (_isdetach)
            {
                // std::cout << "你的线程已经是分离的,不能进行join" << std::endl;
                return true;
            }
            int n = pthread_join(_tid, &_res);
            if (n != 0)
            {
                // std::cerr << "join pthread error" << strerror(n) << std::endl;
                return false;
            }
            else
            {
                // std::cerr << "join pthread sucess" << std::endl;
            }
            return true;
        }
        pthread_t Id()
        {
            return _tid;
        }
        std::string GetName()
        {
            return _name;
        }
        ~Thread() {}

    private:
        pthread_t _tid;    // 线程id
        std::string _name; // 线程名字
        bool _isdetach;    // 是否分离,默认不分离
        bool _isrunning;   // 线程状态,是否运行
        void *_res;        // 线程返回值
        func_t _func;      // 线程执行的任务
    };
}

#endif

2.5 InetAddr.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 网络地址和主机地址转换的类

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        // 网络序列转主机序列
        _port = ntohs(addr.sin_port);
        //_ip = inet_ntoa(addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }
    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);
        _addr.sin_port = htons(_port);
    }
    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }
    const struct sockaddr_in &NetAddr()
    {
        return _addr;
    }
    bool operator==(const InetAddr &addr)
    {
        return _ip == addr._ip && _port == addr._port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr() {}

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

2.6 Route.hpp

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

using namespace MutexModule;
using namespace LogModule;
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, (struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }
        // 这个用户一定已经存在了
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除了一个在线用户" << peer.StringAddr();
            DeleteUser(peer);
        }
    }
    ~Route() {}

private:
    // 首次给我发消息,等同于登录
    std::vector<InetAddr> _online_user; // 在线用户
    Mutex _mutex;
};

2.7 ThreadPool.hpp

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

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

    static const int gnum = 5;

    template <typename T>
    class ThreadPool
    {
        void WakeUpAllthread()
        {
            LockGuard lockguard(_mutex);
            if (_sleepnum > 0)
                _cond.BroadCast();
            LOG(LogLevel::INFO) << "唤醒所有的线程";
        }
        void WakeUpOne()
        {
            _cond.Singal();
            LOG(LogLevel::INFO) << "唤醒一个休眠的线程";
        }
        ThreadPool(int num = gnum)
            : _num(num), _isrunning(false), _sleepnum(0)
        {
            for (int i = 0; i < _num; i++)
            {
                _threads.emplace_back(
                    [this]()
                    {
                        HandlerTask();
                    });
            }
        }
        ThreadPool(const ThreadPool<T> &) = default;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = default;
        void Start()
        {
            if (_isrunning)
                return;
            _isrunning = true;
            for (auto &t : _threads)
            {
                t.Start();
                LOG(LogLevel::INFO) << "start new thread success " << t.GetName();
            }
        }

    public:
        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 (_isrunning && _taskq.empty())
                    {
                        ++_sleepnum;
                        _cond.Wait(_mutex);
                        --_sleepnum;
                    }
                    // 线程被唤醒
                    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() - _sleepnum == 0)
                    WakeUpOne();
                return true;
            }
            return false;
        }
        ~ThreadPool() {}

    private:
        std::vector<Thread> _threads;
        int _num; // 线程池中的线程个数
        std::queue<T> _taskq;

        Cond _cond;
        Mutex _mutex;

        bool _isrunning;
        int _sleepnum;
        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;
    };
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;
    template <typename T>
    Mutex ThreadPool<T>::_lock;
}

2.8 UdpClient.cc

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

std::string server_ip;
uint16_t server_port;
int sockfd;
pthread_t id;
void Recv()
{
    while (1)
    {
        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 = "online";
    int n = 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;
        }
    }
}
// ./updclient 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:创建套接字
    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);

    recver.Start();
    sender.Start();

    id = recver.Id();

    recver.Join();
    sender.Join();

    return 0;
}

2.9 UdpServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"

using func_t = std::function<void(int, const std::string &, InetAddr &)>;

using namespace LogModule;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd),
          //_ip(ip),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }
    void Init()
    {
        //  1:创建套接字
        _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:绑定socket信息,主要是ip和端口.
        // 2.1填充socketaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        // 在网络通信的时候,服务器会不会将ip和端口号发送给client?
        // ip和端口号一定会发送到网络

        // ip和端口号是本地存储格式,所以需要将本地格式转换为网络序列!!
        local.sin_port = htons(_port);

        // 1:ip转成4字节  2:4字节转成网络序列  ->in_addr_t inet_addr(const char *cp);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        
        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;

                _func(_sockfd, buffer, client);
            }
        }
    }
    ~UdpServer() {}

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip; // 用的是字符串风格,点分十进制,"192.168.1.1"
    bool _isrunning;
    func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

2.10 UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp" //网络通信的功能
#include "Route.hpp"
#include "ThreadPool.hpp"

using namespace ThreadPoolModule;

using task_t = std::function<void()>;
// ./udpserver ip 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:网络服务器对象提供通信功能
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr &client)
                                                                  { 
                                                                    task_t t=std::bind(&Route::MessageRoute,&r,sockfd,message,client);
                                                                    tp->Enqueue(t); });
    usvr->Init();
    usvr->Start();
    return 0;
}

这个代码是有一点问题的,我们让线程池种的每一个线程都去执行消息路由的功能。路由模块内部存在在线用户列表,有可能在多线程的情况下,有的线程来进行判断,有的线程正在做广播,都需要同时对在线用户的列表访问。所以当多线程访问在线用户的列表的时候,有的线程发送消息,有的线程可能删除用户,就可能会发生错误。最简单的做法就是访问在线用户列表的时候加锁。
我们将网络序列转换成为主机序列的时候,使用的是如下接口:

我们将主机序列转换成为网络序列的时候,使用的是如下接口:

除了上面这几个接口,还有几个接口,如下所示:

1:inet_pton:将一个字符串IP转换为四字节IP。(这个接口更加安全)

2:inet_ntoa:将ip地址从四字节转换为字符串IP。(不推荐使用)

inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果.那么是否需要调用者手动释放呢?

man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区.这个时候不需要我们手动进行释放.

那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?参见如下代码:

运行结果如下:

因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果.

  • 思考:如果有多个线程调用inet_ntoa,是否会出现异常情况呢?
  • 在APUE中,明确提出inet_ntoa不是线程安全的函数;
  • 但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁;
  • 同学们课后自己写程序验证一下在自己的机器上inet_ntoa是否会出现多线程的问题;
  • 在多线程环境下,推荐使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线安全问题;

3:inet_ntop:将ip地址从四字节转换为字符串IP。(这个接口更加安全)

三 、总结接口

3.1 套接字(UDP)

  1. int socket(int domain, int type, int protocol); 创建套接字
  2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 绑定地址端口
  3. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); UDP 收数据 + 获取对端地址
  4. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); UDP 定向发送
  5. int close(int fd); 关闭 fd

3.2 IP 转换

  1. in_addr_t inet_addr(const char *cp); 字符串 IP→网络序整数,过时
  2. char *inet_ntoa(struct in_addr in); 网络 IP→字符串,非线程安全
  3. int inet_pton(int af, const char *src, void *dst); 字符串 IP→二进制 IP,推荐
  4. const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 二进制 IP→字符串,推荐

3.3 字节序

  1. uint16_t htons(uint16_t hostshort); 主机序短→网络序(端口)
  2. uint16_t ntohs(uint16_t netshort); 网络序短→主机序

3.4 pthread 线程

  1. int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 创建线程
  2. int pthread_join(pthread_t thread, void **retval); 等待回收线程
  3. int pthread_detach(pthread_t thread); 设置分离
  4. int pthread_cancel(pthread_t thread); 发送取消请求
  5. int pthread_getname_np(pthread_t thread, char *name, size_t len); 获取线程名(GNU 扩展)

3.5 互斥锁

  1. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 初始化锁
  2. int pthread_mutex_lock(pthread_mutex_t *mutex); 加锁
  3. int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁
  4. int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁锁

3.6 条件变量

  1. int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); 初始化条件变量
  2. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 等待
  3. int pthread_cond_signal(pthread_cond_t *cond); 唤醒一个
  4. int pthread_cond_broadcast(pthread_cond_t *cond); 唤醒全部
  5. int pthread_cond_destroy(pthread_cond_t *cond); 销毁条件变量
相关推荐
xixiaoyunya4 小时前
搭一个浏览器即用的二进制文件分发点:目录规范与权限边界实践
网络·windows
李&@杰5 小时前
小型园区网从零到精通
网络·tcp/ip·华为
Liuqy-055 小时前
TCP与UDP
网络·网络协议·tcp/ip·udp·c
车载中间件通信5 小时前
[TC8测试]TCP_PROBING_WINDOWS_05
网络·网络协议·tcp/ip·tc8
曦夜日长5 小时前
主从 Reactor 高并发服务器实战 (三):服务器套接字Socket模块详解与实现
运维·服务器·网络
鹿鸣天涯6 小时前
2026年下半年网络规划设计师论文
网络·计算机网络·网络规划设计师
echome8886 小时前
Linux 定时任务:crontab 写法与常见失效原因
linux·运维·网络·定时任务·crontab
从琳开始9896 小时前
从Socket到TCP回显服务器:Java网络编程完整指南
java·服务器·网络·tcp/ip·java-ee
誰能久伴不乏6 小时前
用 UDP 做一条“可靠“的控制通道
c++·qt·网络协议·udp