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;
};
相关推荐
安科士andxe4 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
YJlio6 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
CTRA王大大7 小时前
【网络】FRP实战之frpc全套配置 - fnos飞牛os内网穿透(全网最通俗易懂)
网络
小白同学_C7 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖7 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
testpassportcn7 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
通信大师8 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
不做无法实现的梦~8 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
Tony Bai9 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
消失的旧时光-194310 小时前
从 0 开始理解 RPC —— 后端工程师扫盲版
网络·网络协议·rpc