Socket编程UDP

1.认识接口

socket

  • 创建通信端点:创建一个套接字文件(通信用)
  • 返回值:一个文件描述符,创建成功一定是3。
  • 参数domain:域(套接字应用在哪些领域,网络/本地,AF_INET/AF_UNIX)
  • 参数type:面向数据报通信,还是面向字节流通信。
  • 参数protocol:协议,直接设置为0,系统调用可以通过参数type来决定通信协议,所以可以直接传0(TCP/UDP)

头文件三剑客

这三个头文件包括网络通信编程中要用到的各种变量类型,各种函数声明。

  • 函数:将点分十进制字符串传换成4字节ip地址

sockaddr_in结构体

  • 描述网络套接字地址的一个结构体

  • sin_zero:是为了让结构体常驻符合要求设计的填充字段,内容不重要,但是要存在。

  • sin_addr:就是ip地址

  • _SOCKADDR_COMMON (sin); :宏替换,在宏当中双井号(##),在预处理时让双井号两边的名称符号进行拼接。该宏的结构就是==>sin_family(协议家族)

  • 例:abc##123 预处理后 abc123

  • sa_family_t:就是16位地址类型(AF_INET)

bzero

  • 对指定的数组空间或指针空间全部清零

bind

  • 给套接字文件设置ip地址和端口号
  • 参数sockfd:套接字文件描述符
  • 参数addr:填充好的sockaddr_in结构体,子类对象传给基类对象
  • 参数addrlen:结构体的长度

recvfrom

  • UDP从通信文件中读数据(面向数据报)
  • 返回值:读取的字节数,-1表示失败
  • 参数1:套接字文件描述符
  • 参数buf和len:读取缓冲区,用户提供,保存收到的数据(接收的数据本身)。
  • 参数flags:读取时,是阻塞读取还是非阻塞读取,设置位为0,表示阻塞读取。
  • 阻塞读取:进程读取资源时,如果资源没有就绪,进程就会被阻塞住,服务器进程在读取套接字文件时,如果文件中没有客户端发来的消息,服务端就会被阻塞。
  • src_addr:发送该数据报的sockaddr_in结构体(客户端是谁,ip,port),addrlen为该结构体的长度(这两个参数为输出型参数)。

sendto

  • 向指定的客户端发送消息
  • 发送成功返回实际发送的字节数。
  • sendto在发送时参数要回答3个问题:1.发送什么数据?2.发送给谁?3. 想怎么发送?
  • 发送什么数据:参数2和参数3,buf and len。
  • 发送给谁:参数5和参数6,recvfrom接收的两个输出型参数src_addr , addrlen,再传进该函数中。
  • 怎么发送:UDP协议是一种全双工的协议,任何同一个时刻通信的双方可以同时给对方发送信息(全双工),通过文件描述符进行发送,参数1。
  • falgs:默认为0,数据不发完,sendto不返回。

inet_ntoa

  • 4字节ip转成字符串

2.EchoServer

2.1第一步框架

cpp 复制代码
#ifndef __ECHOSERVER_HPP
#define __ECHOSERVER_HPP

#include <iostream>
#include<stdlib.h>
#include<sys/socket.h>

#include "Logger.hpp"

using namespace NS_LOG_MODULE;

const static int default_fd=-1;

enum
{
    SUCCESS=0,
    SOCKET_ERR,
};

class UdpServer
{
public:
    UdpServer():_sockfd(default_fd)
    {

    }
    ~UdpServer()
    {
        
    }
    void Init()
    {
        //内核定义
        //#define AF_INET		PF_INET
        //#define PF_INET		2	/* IP protocol family.  */
        //SOCK_DGRAM = 2,		/* Connectionless, unreliable datagrams
		//of fixed maximum length.  */
        //#define SOCK_DGRAM SOCK_DGRAM
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket failed, errno: ";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"create socket success, sockfd: "<<_sockfd;
    }
    void Start()
    {

    }
private:
    //套接字文件描述符
    int _sockfd;
};

#endif
cpp 复制代码
//服务端
#include"EchoServer.hpp"
#include<memory>

int main()
{
    ENABLE_CONSOLE_LOG_STRATEGY();//启用控制台日志刷新策略
    //创建服务器对象
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
    //初始化服务器
    usvr->Init();
    //任何软件都是一个死循环,服务器也是一样的,服务器需要一直运行,等待客户端的连接
    usvr->Start();
    return 0;
}

2.2编写结束

cpp 复制代码
#ifndef __ECHOSERVER_HPP
#define __ECHOSERVER_HPP

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

#include "Logger.hpp"

using namespace NS_LOG_MODULE;

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

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

class UdpServer
{
public:
    //UdpServer( const std::string& ip,uint16_t port = default_port)
    UdpServer(uint16_t port = default_port)
    :_sockfd(default_fd),
    _port(port)
    {

    }
    ~UdpServer()
    {
        close(_sockfd);
    }
    void Init()
    {
        //内核定义
        //#define AF_INET		PF_INET
        //#define PF_INET		2	/* IP protocol family.  */
        //SOCK_DGRAM = 2,		/* Connectionless, unreliable datagrams
		//of fixed maximum length.  */
        //#define SOCK_DGRAM SOCK_DGRAM
        //创建套接字,本质:打开网卡---系统特性
        //这里传AF_INET,说明我们要打开一个IPv4的网卡文件;传SOCK_DGRAM,说明我们要打开一个UDP的网卡文件;传0,说明我们要使用默认的协议(IPPROTO_UDP)
        //说白了就是告诉系统我们要创建一个网络套接字文件。
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket failed, errno: ";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"create socket success, sockfd: "<<_sockfd;



        //第二步:为网卡文件填充网络信息
        struct sockaddr_in local;
        //因为该结构体有填充字节,所以我们需要先将其清零,才能进行后续的赋值操作。
        bzero(&local,sizeof(local));

        //这里也需要传AF_INET,为了在继承体系下,让函数辨别出来我是要进行网络通信。
        local.sin_family=AF_INET;
        local.sin_port=htons(_port);//端口号需要进行字节序转换
        //服务端不要显示bind ip地址,要任意地址绑定
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());//IP地址需要进行点分十进制字符串转换成4字节IP地址
        local.sin_addr.s_addr=INADDR_ANY;//任意绑定
        //此时local结构体填充好后,我们的ip和port信息并没有设置到内核中,没有设置到刚打开的网络socket文件中。
        //sockaddr_in就是标准库提供的一种数据类型,和整形int,double一样,所以该local变量是被定义在用户栈上的,并没有设置到内核。



        //第三步:把前两步进行合并,绑定ip和端口
        int n =bind(_sockfd,(struct sockaddr*)&local,sizeof(local));//失败返回-1,成功返回0
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"bind socket failed, errno: ";
            exit(bind_ERR);
        }
        LOG(LogLevel::INFO)<<"bind socket success, sockfd: "<<_sockfd<<", port: "<<_port;

    }
    void Start()
    {
        //用户发来的数据,默认双方传递的是字符串,echo_server收到数据后,原封不动的把数据发回去。
        char inbuffer[1024];
        while(true)
        {
            //UDP是面向数据报的,不是字节流,无法使用read和write进行通信
            //服务器先读后写
            struct sockaddr_in peer;//接收远端的套接字信息
            socklen_t len=sizeof(peer);
            ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);
            //运行到这时,peer中就存储了远端的ip和port信息,len中存储了peer的长度
            if(n>0)
            {
                //用户的IP和port信息存储在peer中,是recvfrom从网络中获取到的数据
                //该结构体中的数据是网络序列(大端)-》转换成主机序列
                uint16_t client_port=ntohs(peer.sin_port);
                std::string client_ip = inet_ntoa(peer.sin_addr);
                std::string client_address ="[" + client_ip + ":" + std::to_string(client_port) + "]#";

                //向文件中写入数据时,不应该把\0写入文件中,因为\0是c语言的规定,不建议写入。
                inbuffer[n]=0;//手动的把收到的数据末尾添加\0 
                //LOG(LogLevel::DEBUG)<<"client say# "<<inbuffer;
                LOG(LogLevel::DEBUG)<<client_address<<inbuffer;
                std::string echo_string="server echo#" + std::string(inbuffer);//inbuffer的临时对象。

                //把收到的数据返回给客户端
                //此时peer中的ip和端口就是网络序列,无需转换
                sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
            }
            else
            {
                LOG(LogLevel::ERROR)<<"recvfrom failed, errno: ";
                
            }

        }
    }
private:
    //套接字文件描述符
    int _sockfd;
    //服务器层面不要设置对应的ip
    //std::string _ip;//ip有两种形态:点分十进制字符串("192.168.2.2")和4字节ip(内核用)
    uint16_t _port;//用户设置好的,server port必须是固定的;
};

#endif
cpp 复制代码
//服务端
#include"EchoServer.hpp"
#include<memory>

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

//./server_udp ip port
//服务器启动部需要ip
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    ENABLE_CONSOLE_LOG_STRATEGY();//启用控制台日志刷新策略

    //std::string server_ip=argv[1];
    uint16_t server_port=std::stoi(argv[1]);
    //创建服务器对象
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(server_port);
    //初始化服务器
    usvr->Init();
    //任何软件都是一个死循环,服务器也是一样的,服务器需要一直运行,等待客户端的连接
    usvr->Start();
    return 0;
}
cpp 复制代码
#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstdlib>

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

//服务端的ip+port ,是被工程师内置到客户端的(硬编码/配置文件)
//./echo_server server_ip server_port
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port =std::stoi(argv[2]);

    //客户端使用也需要先创建套接字
    //网络通信AF_INET,UDP通信SOCK_DGRAM(用户数据报),协议默认0
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        std::cerr<<"create socket failed, errno: "<<errno<<std::endl;
        exit(2);
    }

    //2.两个主机在进行网络通信时,每个主机都要有自己的ip地址和port端口号。
    //客户端也必须要有自己的ip和端口
    //客户端可以显示bind自己的ip和端口号,但是不要显示绑定。

    //为什么不要显示bind?
    //如果要bind,就要指明端口号,在服务器领域,端口号必须是固定的。
    //服务端对应的客户端往往有很多个,服务端的端口号必须是众所周知且不能修改。
    //例如通信软件,客户端启动后必须先连接服务端,客户端通过服务端发来的信息,知道其他客户端的ip和端口号,才能进行客户之间通信。
    //所以客户端必须默认知道服务端的端口号,服务端就不能随便修改。
    //客户端的端口号具体是多少不重要,只需要具有唯一性即可。
    //如果不同公司的客户端都显示绑定端口号,同时它们的客户端都想绑定一个吉利的数字(6666),那么用户就无法同时启动这些客户端,会产生冲突。
    //客户端一般采用随机端口的形式,由OS自主选择。

    //任何客户端都是先发数据,udp client首次发送数据时,OS底层会隐式自动帮client进行获取随机端口,bind。
    //服务端必须显示绑定端口号,如果不显示绑定,没人能知道服务端口号,客户端无法获取服务端的端口号,无法连接服务器。
    //比如紧急电话的110和120,大家都知道是固定的号码,不能随便更改,但是我们自己的电话号码是什么不重要,只需要具有唯一性即可。
    //公司内部上线的软件的服务端的端口号是被统一管理的,不会出现冲突。

    //填充服务端的sockaddr_in
    struct sockaddr_in server;
    memset(&server,0,sizeof(0));//将结构体置零
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=inet_addr(server_ip.c_str());
    server.sin_port=htons(server_port);


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

        //2.把用户输入的数据发送给服务器,首次发送自动绑定。
        ssize_t n=sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
        if(n>0)
        {
            //读消息
            char inbuffer[1024];
            struct sockaddr_in temp;
            socklen_t len=sizeof(temp);
            ssize_t m=recvfrom(sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&temp,&len);
            if(m>0)
            {
                inbuffer[m]=0;
                std::cout<<inbuffer<<std::endl;
            }
        }

    }

    return 0;
}
cpp 复制代码
#pragma once

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

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

class LockGuard // RAII风格代码
{
public:
    LockGuard(Mutex &lock):_lockref(lock)
    {
        _lockref.Lock();
    }
    ~LockGuard()
    {
        _lockref.Unlock();
    }
private:
    Mutex &_lockref;
};
cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

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

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

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

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

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

    private:
        Mutex _mutex;
    };

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


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

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

        ~FileLogStrategy()
        {
        }

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

    // 交给大家
    // const std::string defaultfilename = "log.info";
    // const std::string defaultfilename = "log.warning";
    // const std::string defaultfilename = "log.fatal";
    // const std::string defaultfilename = "log.error";
    // const std::string defaultfilename = "log.debug";
     // 文件策略&&分日志等级来进行保存
    // class FileLogLevelStrategy : public LogStrategy
    // {
    // public:
    // private:
    // };


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

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

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

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

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

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

        ~Logger()
        {
        }

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

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

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

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

}

#endif
  • 对网络通信代码进行测试时,可以使用本地测试,ip=127.0.0.1 ,port随便写
  • 127.0.0.1为本地环回ip地址,报文不会发送到网络,只会在自己的网络协议栈中跑一次。
  • 通常用于本地通信和网络代码的测试。

最佳实践

1.云服务器的公网ip是禁止被显示bind的。

2.在服务器开发的时候,特别不建议用户显示 bind IP地址

3.服务器内部会有很多种ip,甚至多张网卡。

4.如果服务器显示的绑定了一个ip地址,进程从内核中获取数据时,只能拿取发送给自己绑定ip地址的报文。

5.任意地址绑定,一台服务器上有多个ip地址,不管客户端发送给哪个ip地址,服务端进程都能收到数据,这就是云服务器厂商禁止用户显示绑定某一个ip的原因。

3.windows作为客户端的代码

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>

#pragma warning(disable : 4996)

#pragma comment(lib, "ws2_32.lib")//关联静态库

std::string serverip = "";  // 填写你的云服务器ip
uint16_t serverport = 8888; // 填写你的云服务开放的端口号

int main()
{
    WSADATA wsd;//版本号
    WSAStartup(MAKEWORD(2, 2), &wsd);//初始化库,2.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());
    //创建套接字
    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == SOCKET_ERROR)//就是-1
    {
        std::cout << "socker error" << std::endl;
        return 1;
    }
    std::string message;
    char buffer[1024];
    while (true)
    {
        std::cout << "Please Enter@ ";
        std::getline(std::cin, message);
        if (message.empty()) continue;
        sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));
        struct sockaddr_in temp;
        int len = sizeof(temp);
        int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }

    closesocket(sockfd);//关闭套接字文件
    WSACleanup();//关闭库
    return 0;
}

服务端一般都是linux系统。

4.简单英译汉网络字典

cpp 复制代码
#ifndef __ECHOSERVER_HPP
#define __ECHOSERVER_HPP

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

#include "Logger.hpp"

using namespace NS_LOG_MODULE;

const static int default_fd=-1;
const static int default_port=8080;
//回调函数类型
using callback_t = std::function<std::string(std::string)>;

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

class UdpServer
{
public:
    //UdpServer( const std::string& ip,uint16_t port = default_port)
    UdpServer(callback_t cb , uint16_t port = default_port)
    :_sockfd(default_fd),
    _port(port),
    _cb(cb)
    {

    }
    ~UdpServer()
    {
        close(_sockfd);
    }
    void Init()
    {
        //内核定义
        //#define AF_INET		PF_INET
        //#define PF_INET		2	/* IP protocol family.  */
        //SOCK_DGRAM = 2,		/* Connectionless, unreliable datagrams
		//of fixed maximum length.  */
        //#define SOCK_DGRAM SOCK_DGRAM
        //创建套接字,本质:打开网卡---系统特性
        //这里传AF_INET,说明我们要打开一个IPv4的网卡文件;传SOCK_DGRAM,说明我们要打开一个UDP的网卡文件;传0,说明我们要使用默认的协议(IPPROTO_UDP)
        //说白了就是告诉系统我们要创建一个网络套接字文件。
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            LOG(LogLevel::FATAL)<<"create socket failed, errno: ";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"create socket success, sockfd: "<<_sockfd;



        //第二步:为网卡文件填充网络信息
        struct sockaddr_in local;
        //因为该结构体有填充字节,所以我们需要先将其清零,才能进行后续的赋值操作。
        bzero(&local,sizeof(local));

        //这里也需要传AF_INET,为了在继承体系下,让函数辨别出来我是要进行网络通信。
        local.sin_family=AF_INET;
        local.sin_port=htons(_port);//端口号需要进行字节序转换
        //服务端不要显示bind ip地址,要任意地址绑定
        //local.sin_addr.s_addr=inet_addr(_ip.c_str());//IP地址需要进行点分十进制字符串转换成4字节IP地址
        local.sin_addr.s_addr=INADDR_ANY;//任意绑定
        //此时local结构体填充好后,我们的ip和port信息并没有设置到内核中,没有设置到刚打开的网络socket文件中。
        //sockaddr_in就是标准库提供的一种数据类型,和整形int,double一样,所以该local变量是被定义在用户栈上的,并没有设置到内核。



        //第三步:把前两步进行合并,绑定ip和端口
        int n =bind(_sockfd,(struct sockaddr*)&local,sizeof(local));//失败返回-1,成功返回0
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"bind socket failed, errno: ";
            exit(bind_ERR);
        }
        LOG(LogLevel::INFO)<<"bind socket success, sockfd: "<<_sockfd<<", port: "<<_port;

    }
    void Start()
    {
        //用户发来的数据,默认双方传递的是字符串,echo_server收到数据后,原封不动的把数据发回去。
        char inbuffer[1024];
        while(true)
        {
            //UDP是面向数据报的,不是字节流,无法使用read和write进行通信
            //服务器先读后写
            struct sockaddr_in peer;//接收远端的套接字信息
            socklen_t len=sizeof(peer);
            ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);
            //运行到这时,peer中就存储了远端的ip和port信息,len中存储了peer的长度
            if(n>0)
            {
                //用户的IP和port信息存储在peer中,是recvfrom从网络中获取到的数据
                //该结构体中的数据是网络序列(大端)-》转换成主机序列
                uint16_t client_port=ntohs(peer.sin_port);
                std::string client_ip = inet_ntoa(peer.sin_addr);
                std::string client_address ="[" + client_ip + ":" + std::to_string(client_port) + "]#";

                //向文件中写入数据时,不应该把\0写入文件中,因为\0是c语言的规定,不建议写入。
                inbuffer[n]=0;//手动的把收到的数据末尾添加\0 
                // //LOG(LogLevel::DEBUG)<<"client say# "<<inbuffer;
                // LOG(LogLevel::DEBUG)<<client_address<<inbuffer;
                // std::string echo_string="server echo#" + std::string(inbuffer);//inbuffer的临时对象。

                std::string result = _cb(inbuffer);//单词

                //把收到的数据返回给客户端
                //此时peer中的ip和端口就是网络序列,无需转换
                sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);
            }
            else
            {
                LOG(LogLevel::ERROR)<<"recvfrom failed, errno: ";
                
            }

        }
    }
private:
    //套接字文件描述符
    int _sockfd;
    //服务器层面不要设置对应的ip
    //std::string _ip;//ip有两种形态:点分十进制字符串("192.168.2.2")和4字节ip(内核用)
    uint16_t _port;//用户设置好的,server port必须是固定的;
    callback_t _cb;//用回调的方式进行数据加工
};

#endif
cpp 复制代码
#pragma once

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

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

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

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

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

private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
};
cpp 复制代码
//服务端
#include"Dict.hpp"
#include"EchoServer.hpp"
#include<memory>

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

//./server_udp ip port
//服务器启动部需要ip
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    ENABLE_CONSOLE_LOG_STRATEGY();//启用控制台日志刷新策略
    //1.定义字典
    Dict dict;

    //2.构建网络服务处理IO问题
    //std::string server_ip=argv[1];
    uint16_t server_port=std::stoi(argv[1]);
    //创建服务器对象
    //3.绑定上下两层
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&dict](std::string word)->std::string{
        return dict.Translate(word);
    }, 
    server_port);
    //初始化服务器
    usvr->Init();
    //任何软件都是一个死循环,服务器也是一样的,服务器需要一直运行,等待客户端的连接
    usvr->Start();
    return 0;
}

5.简单聊天室

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Thread.hpp"
#include "InetAddr.hpp"

using namespace NS_THREAD_MODULE;

int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
std::string nickname;

static void Usage(const std::string &proc)
{
    std::cout << "Usage:\n\t";
    std::cout << proc << " server_ip server_port" << std::endl;
}
static void Online(InetAddr &serveraddr)
{
    std::cout << "Please Set Your Nick Name# ";
    std::getline(std::cin, nickname);
    std::string online_message = nickname + " online!";
    ssize_t n = sendto(sockfd, online_message.c_str(), online_message.size(), 0,
                       (struct sockaddr *)serveraddr.GetNetAddress(), serveraddr.Len());
    (void)n;
}

void RecvMessage()
{
    while (true)
    {
        // recvfrom
        char inbuffer[1024] = {0};
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t m = recvfrom(sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&temp, &len);
        if (m > 0)
        {
            inbuffer[m] = 0;
            std::cerr << inbuffer << std::endl; // 2
        }
    }
}

void SendMessage()
{
    InetAddr serveraddr(server_port, server_ip);
    Online(serveraddr);

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

        message = nickname + "# " + message;

        // 2. clinet 发送数据给 server,首次发送即自动bind
        ssize_t n = sendto(sockfd, message.c_str(), message.size(), 0,
                           (struct sockaddr *)serveraddr.GetNetAddress(), serveraddr.Len());
        (void)n;
    }
}

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

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

    Thread recver(RecvMessage);
    Thread sender(SendMessage);

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

    recver.Join();
    sender.Join();
    return 0;
}
cpp 复制代码
#include "ThreadPool.hpp" // 执行者,执行处理动作的人
#include "Route.hpp"   // 任务
#include "UdpServer.hpp" // 获取事件
#include <memory>

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

using namespace NS_THREAD_POOL_MODULE;

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

// ./server_udp port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    ENABLE_CONSOLE_LOG_STRATEGY();
    uint16_t server_port = std::stoi(argv[1]);
    
    // 线程池模块
    auto thread_pool = ThreadPool<task_t>::Instance();

    // 路由模块
    Route r;

    // 网络模块
    UdpServer usvr(server_port);
    usvr.Init();

    usvr.RegisterService(
        [&r](const InetAddr &addr){
            r.CheckUser(addr);
        },
        [&r, thread_pool](int sockfd, std::string msg){
            auto t = std::bind(&Route::Broadcast, &r, sockfd, msg);
            thread_pool->Enqueue(t);
            // thread_pool->Enqueue([&r, &sockfd, &msg](){
            //     r.Broadcast(sockfd, msg);
            // });
        }
    );

    usvr.Start();
    return 0;
}
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)
    {
        int n = pthread_cond_wait(&_cond, mutex.Ptr());
        (void)n;
    }
    void Signal()
    {
        int n = pthread_cond_signal(&_cond);
        (void)n;
    }
    void Broadcast()
    {
        int n = pthread_cond_broadcast(&_cond);
        (void)n;
    }
    ~Cond()
    {
        pthread_cond_destroy(&_cond);
    }
private:
    pthread_cond_t _cond;
};

#endif
cpp 复制代码
#pragma once

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


// 对客户端进行先描述
class InetAddr
{
public:
    InetAddr(const struct sockaddr_in &address):_address(address), _len(sizeof(address))
    {
        _ip = inet_ntoa(_address.sin_addr);
        _port = ntohs(_address.sin_port);
    }
    InetAddr(uint16_t port, const std::string &ip = "0.0.0.0"):_ip(ip), _port(port)
    {
        bzero(&_address, sizeof(_address));
        _address.sin_family = AF_INET;
        _address.sin_port = htons(_port);                  // h->n
        _address.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 字符串ip->4字节IP 2. hton
        _len = sizeof(_address);
    }
    bool operator == (const InetAddr &addr)
    {
        return (this->_ip == addr._ip) && (this->_port == addr._port);
    }
    std::string ToString()
    {
        return "[" + _ip + ":" + std::to_string(_port) + "]";
    }
    InetAddr()
    {}
    struct sockaddr_in *GetNetAddress()
    {
        return &_address;
    }
    socklen_t Len()
    {
        return _len;
    }
    ~InetAddr()
    {}
private:
    // net address
    struct sockaddr_in _address;
    socklen_t _len;
    // host address
    std::string _ip;
    uint16_t _port;
};
cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

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

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

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

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

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

    private:
        Mutex _mutex;
    };

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


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

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

        ~FileLogStrategy()
        {
        }

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

    // 交给大家
    // const std::string defaultfilename = "log.info";
    // const std::string defaultfilename = "log.warning";
    // const std::string defaultfilename = "log.fatal";
    // const std::string defaultfilename = "log.error";
    // const std::string defaultfilename = "log.debug";
     // 文件策略&&分日志等级来进行保存
    // class FileLogLevelStrategy : public LogStrategy
    // {
    // public:
    // private:
    // };


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

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

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

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

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

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

        ~Logger()
        {
        }

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

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

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

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

}

#endif
cpp 复制代码
#pragma once

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

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

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

#include <iostream>
#include <memory>
#include <string>
#include <sys/socket.h>
#include "Mutex.hpp"
#include "UserManager.hpp"

class Route
{
public:
    Route():_uma(std::make_unique<UserManager>())
    {}
    void CheckUser(const InetAddr &addr)
    {
        LockGuard lockguard(_lock);
        _uma->AddUser(addr);
    }
    void OfflineUser(const InetAddr &addr)
    {
        LockGuard lockguard(_lock);
        _uma->DelUser(addr);
    }
    void Broadcast(int sockfd, std::string message)
    {
        LockGuard lockguard(_lock);
        auto &users = _uma->Users();
        for(auto &user : users)
        {
            sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)user.GetNetAddress(), user.Len());
        }
    }
    // void Sendto(int sockfd, std::string message, InetAddr & who)
    // {
        
    // }
    ~Route()
    {}
private:
    std::unique_ptr<UserManager> _uma;
    // std::queue<std::string> _q;
    Mutex _lock;
};
cpp 复制代码
#pragma once

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

namespace NS_THREAD_MODULE
{
    static int gnumber = 1;
    using callback_t = std::function<void()>;

    enum class TSTATUS
    {
        THREAD_NEW,
        THREAD_RUNNING,
        THREAD_STOP
    };

    std::string Status2String(TSTATUS s)
    {
        switch (s)
        {
        case TSTATUS::THREAD_NEW:
            return "THREAD_NEW";
        case TSTATUS::THREAD_RUNNING:
            return "THREAD_RUNNING";
        case TSTATUS::THREAD_STOP:
            return "THREAD_STOP";
        default:
            return "UNKNOWN";
        }
    }

    std::string IsJoined(bool joinable)
    {
        return joinable ? "true" : "false";
    }

    class Thread
    {
    private:
        void ToRunning()
        {
            _status = TSTATUS::THREAD_RUNNING;
        }
        void ToStop()
        {
            _status = TSTATUS::THREAD_STOP;
        }
        static void *ThreadRoutine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            pthread_setname_np(self->_tid, self->_name.c_str());
            self->_cb();
            self->ToStop();
            return nullptr;
        }

    public:
        Thread(callback_t cb)
            : _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr)
        {
            _name = "Slaver-" + std::to_string(gnumber++);
        }
        bool Start()
        {
            int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
            if (n != 0)
                return false;

            ToRunning();
            return true;
        }
        void Join()
        {
            if (_joinable)
            {
                int n = pthread_join(_tid, &_result);
                if (n != 0)
                {
                    std::cerr << "join error: " << n << std::endl;
                    return;
                }
                (void)_result;
                _status = TSTATUS::THREAD_STOP;
            }
            else
            {
                std::cerr << "error, thread join status: " << IsJoined(_joinable) << std::endl;
            }
        }
        // 暂停
        // void Stop() // restart()
        // {
        //     // 让线程暂停
        // }
        void Die()
        {
            if (_status == TSTATUS::THREAD_RUNNING)
            {
                pthread_cancel(_tid);
                _status = TSTATUS::THREAD_STOP;
            }
        }
        void Detach()
        {
            if (_status == TSTATUS::THREAD_RUNNING && _joinable)
            {
                pthread_detach(_tid);
                _joinable = false;
            }
            else
            {
                std::cerr << "detach " << _name << " failed" << std::endl;
            }
        }
        void PrintInfo()
        {
            std::cout << "thread name : " << _name << std::endl;
            std::cout << "thread _tid : " << _tid << std::endl;
            std::cout << "thread _status : " << Status2String(_status) << std::endl;
            std::cout << "thread _joinable : " << IsJoined(_joinable) << std::endl;
        }

        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        TSTATUS _status;
        bool _joinable;
        // 线程要有自己的任务处理,即回调函数
        callback_t _cb;

        // 线程退出信息
        void *_result;
    };
}
cpp 复制代码
#pragma once

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

namespace NS_THREAD_POOL_MODULE
{
    using namespace NS_LOG_MODULE;
    using namespace NS_THREAD_MODULE;

    const int defaultnum = 5;

    // void Test()
    // {
    //     char name[128];
    //     pthread_getname_np(pthread_self(), name, sizeof(name));
    //     while (true)
    //     {
    //         LOG(LogLevel::DEBUG) << "我是一个线程,我要进行运行:" << name;
    //         sleep(1);
    //     }
    // }

    // 线程池要不要对多个线程进行管理呢??
    // 先描述,在组织!
    template <typename T>
    class ThreadPool
    {
    private:
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T task;
                {
                    // 保护临界区
                    LockGuard lockguard(_mutex);
                    // 检测任务。不休眠:1. 队列不为空 2. 线程池退出 -> 队列为空 && 线程池不退出
                    while (_tasks.empty() && _isrunning)
                    {
                        // 没有任务, 休眠
                        _slaver_sleep_count++;
                        _cond.Wait(_mutex);
                        _slaver_sleep_count--;
                    }
                    // 线程池退出了-> while 就要break -> 不能
                    // 1. 线程池退出 && _tasks empty
                    if (!_isrunning && _tasks.empty())
                    {
                        _mutex.Unlock();
                        break;
                    }

                    // 有任务, 取任务,本质:把任务由公共变成私有
                    // T -> task*
                    task = _tasks.front();
                    _tasks.pop();
                }

                // 处理任务, 约定
                // 处理任务需要再临界区内部处理吗?不需要
                LOG(LogLevel::INFO) << name << "处理任务:";
                task();
            }

            // 线程退出
            LOG(LogLevel::INFO) << name << " quit...";
        }

        ThreadPool(int slaver_num = defaultnum) : _isrunning(false), _slaver_sleep_count(0), _slaver_num(slaver_num)
        {
            // ThreadPool对象已经存在了
            for (int idx = 0; idx < _slaver_num; idx++)
            {
                // auto f = std::bind(&ThreadPool::HandlerTask, this);
                // // auto f = [this](){
                // //      this->HandlerTask();
                // // };
                // _slavers.emplace_back(f);
                _slavers.emplace_back([this]()
                                      { this->HandlerTask(); });
            }
        }
        // 赋值 拷贝构造禁止
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
        ThreadPool(const ThreadPool<T> &) = delete;

    public:
        // 如果多线程获取这个单例呢.加锁
        // 多线程安全了,但是效率比较低,双if判断
        static ThreadPool<T> *Instance()
        {
            if(nullptr == _instance) // 双if判断
            {
                // 多线程
                LockGuard lockguard(_lock);
                if (nullptr == _instance)
                {
                    // 第一次调用
                    _instance = new ThreadPool<T>();
                    _instance->Start();
                    LOG(LogLevel::INFO) << "第一次使用线程池,创建线程池对象";
                }
            }
            return _instance;
        }
        void Start()
        {
            if (_isrunning)
            {
                LOG(LogLevel::WARNING) << "Thread Pool Is Already Running";
                return;
            }
            _isrunning = true;
            for (auto &slave : _slavers)
            {
                slave.Start();
            }
        }
        void Stop()
        {
            // version1 -- 后续调整
            // if (!_isrunning)
            // {
            //     LOG(LogLevel::WARNING) << "Thread Pool Is Not Running";
            //     return;
            // }
            // for (auto &slave : _slavers)
            // {
            //     slave.Die(); // 太简单粗暴了
            // }
            // _isrunning = false;
            // version 2
            // 1. _isrunning = false
            // 2. 处理完成tasks所有的任务
            // 线程状态: 休眠,正在处理任务 -> 让所有线程全部唤醒
            // HandlerTask自动break
            _mutex.Lock();
            _isrunning = false;
            if (_slaver_sleep_count > 0)
                _cond.Broadcast();
            _mutex.Unlock();
        }
        void Wait()
        {
            for (auto &slave : _slavers)
            {
                slave.Join();
            }
        }
        void Enqueue(T in)
        {
            _mutex.Lock();
            _tasks.push(in);
            if (_slaver_sleep_count > 0)
                _cond.Signal();
            _mutex.Unlock();
        }
        ~ThreadPool()
        {
        }

    private:
        bool _isrunning;
        int _slaver_num;
        std::vector<Thread> _slavers;
        std::queue<T> _tasks; // 任务队列,临界资源
        Mutex _mutex;
        Cond _cond;
        int _slaver_sleep_count;

        // 添加单例模式
        static ThreadPool<T> *_instance;
        static Mutex _lock; // 保证单例的安全
    };

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

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

}
cpp 复制代码
#ifndef __ECHOSERVER_HPP
#define __ECHOSERVER_HPP

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

using namespace NS_LOG_MODULE;

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

using handler_addr_t = std::function<void (const InetAddr &)>;
using handler_msg_t = std::function<void (int sokcfd, std::string msg)>;

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

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

        // 第三步:bind socket 信息
        int n = bind(_sockfd, (struct sockaddr *)(local.GetNetAddress()), local.Len());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind socket error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind socket success"<< ", port: " << _port;
    }
    void RegisterService(handler_addr_t handler_addr, handler_msg_t handler_msg)
    {
        _handler_addr = handler_addr;
        _handler_msg = handler_msg;
    }
    void Start()
    {
        // 传递的是字符串,echo server
        char inbuffer[1024];
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 用户发来的数据
            // 2. 用户的socket信息
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                inbuffer[n] = 0;
                // 1. 检测新用户
                InetAddr clientaddress(peer);
                std::string tips = clientaddress.ToString();
                std::string message = tips + inbuffer;
                LOG(LogLevel::DEBUG) << message;
                _handler_addr(clientaddress);
                // 2. 转发消息

                // nickname# message -> [ip:port]-nickname# message

                _handler_msg(_sockfd, message);
            }
            else
            {
                LOG(LogLevel::ERROR) << "recvfrom error";
            }
            // sleep(3);
        }
    }

private:
    int _sockfd;
    // std::string _ip; // "192.168.2.2"(字符串风格的点分十进制IP地址, 让人看的) && 4字节IP ???
    uint16_t _port;  // 用户设置好的,server port必须是固定的!
    handler_addr_t _handler_addr;
    handler_msg_t _handler_msg;
};

#endif
cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Logger.hpp"

using namespace NS_LOG_MODULE;

// class User
// {
//     std::string username;
//     std::string userstatus;
//     InetAddr address;
//     ...
// }

// 增删查改
class UserManager
{
public:
    UserManager(){}
    void AddUser(const InetAddr &addr)
    {
        if(SearchUser(addr))
            return;
        _users.push_back(addr);
    }
    void DelUser(const InetAddr &addr)
    {
        // if(!SearchUser(addr))
        //     return;

        for(auto iter = _users.begin(); iter != _users.end(); iter++)
        {
            if(*iter == addr)
            {
                _users.erase(iter);
                break;
            }
        }
    }
    bool SearchUser(const InetAddr &addr)
    {
        for(auto &user : _users)
        {
            if(user == addr)
            {
                return true;
            }
        }
        return false;
    }
    bool ModUser(const InetAddr &addr)
    {
        // 简单一点
        DelUser(addr);
        AddUser(addr);
        return true;
    }
    std::vector<InetAddr> &Users()
    {
        return _users;
    }
    ~UserManager(){}
private:
    // "ip:port" -> InetAddr
    std::vector<InetAddr> _users;
};
相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao3 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush44 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5204 天前
Linux 11 动态监控指令top
linux
网络研究院4 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展