当前位置: 首页 > news >正文

Socket编程UDP

目录

  • 一、V1 版本-echo server
    • 1、服务端的实现
      • (1)创建套接字 socket
      • (2)绑定 bind
        • (i)sockaddr_in结构体
        • (ii)结构体初始化--bzero
        • (iii)结构体数据填充-协议家族
        • (iv)结构体数据填充--大小端转化问题
        • (v)设置进内核
      • (3)启动服务器
      • (4)IP的绑定
        • (i)ifconfig
        • (ii)netstat
      • (5)读取数据 recvfrom
        • (i)inet_ntoa函数
      • (6)服务端整体代码
    • 2、客户端实现
      • (1)创建套接字
      • (2)绑定问题
      • (3)发送信息sendto
    • 3、源码简易版
      • (1)UdpServer.hpp
      • (2)Server.cc
      • (3)Client.cc
    • 4、源码封装版
      • (1)UdpServer.hpp
      • (2)UdpServerMain.cc
      • (3)UdpClient.hpp
      • (4)UdpClientMain.cc
      • (5)Mutex.hpp
      • (6)Makefile
      • (7)Log.hpp
      • (8)InetAddr.hpp
      • (9)Common.hpp
    • 5、加餐--网络命令
      • (1)Ping命令
      • (2)pidof
  • 二、V2 版本-DictServer
    • 1、UdpServer.hpp
    • 2、UdpServerMain.cc
    • 3、UdpClient.hpp
    • 4、UdpClientMain.cc
    • 5、Mutex.hpp
    • 6、Makefile
    • 7、Log.hpp
    • 8、InetAddr.hpp
    • 9、Dictionary.hpp
    • 10、Common.hpp
    • 11、dict.txt
  • 三、V3 版本-ChatServer
    • 1、UdpServer.hpp
    • 2、UdpServerMain.cc
    • 3、UdpClient.hpp
    • 4、UdpClientMain.cc
    • 5、Mutex.hpp
    • 6、ThreadPool.hpp
    • 7、Thread.hpp
    • 8、User.hpp
    • 9、Makefile
    • 10、Log.hpp
    • 11、InetAddr.hpp
    • 12、Cond.hpp
    • 13、Common.hpp

一、V1 版本-echo server

1、服务端的实现

(1)创建套接字 socket

在通信之前要先把网卡文件打开,函数作用:打开一个文件,把文件和网卡关联起来。

#include <sys/types.h>     
#include <sys/socket.h>
 
int socket(int domain, int type, int protocol);

参数说明:

  • domain:域,标识了这个套接字的通信类型(网络通信/本地通信),这个就是sockaddr结构体的前16个bit位。如果是本地通信就是AF_UNIX,如果是网络通信就是AF_INET。
  • type:套接字提供的服务类型,常见的就是SOCK_STREAM和SOCK_DGRAM,如果我们是基于TCP协议的通信,就使用SOCK_STREAM,表示的是流式服务。如果我们是基于UDP协议通信的,就使用SOCK_DGRAM,表示的是数据包服务。
  • protocol:创建套接字的类型(TCP/UDP),但是这个参数可以由前两个参数决定。所以通常设置为0

返回值:

成功会返回一个文件描述符,失败返回-1,错误码被设置

在系统的文件操作中,也会返回文件描述符,与socket返回的文件描述符不同的是,普通文件的文件缓冲区对应的是磁盘,而socket返回的文件描述符的文件缓冲区对应的是网卡。用户将数据写到缓冲区,由操作系统自动将缓冲区中的数据刷新到网卡中,网卡会负责将这个数据发送到对端主机上。

class UdpServer
{
public:
    UdpServer()
    {
        // 创建套接字
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            std::cerr << "create socket error" << std::endl;
            exit(1);
        }
       std::cout << "create socket success, socket: " << _socket << std::endl;
    }
 
    ~UdpServer()
    {
        close(_socket);
    }
 
private:
    int _socket;
};

调用socket函数,就能创建一个套接字了,第一个参数我们填AF_INET,表示的是我们是网络通信。第二个参数我们填SOCK_DGRAM,表示我们是UDP服务(数据报)。由于一个进程启动时,默认会打开标准输入,标准输出,标准错误这三个文件描述符,而文件描述符的创建规则就是从0开始,向上找到第一个没有使用的,所以我们可以猜测以下_sock的值为3.

(2)绑定 bind

我们创建完套接字以后,也只是打开了一个文件。还没有将这个文件和网络关联起来,所以我们需要使用bind函数,将IP+port和文件绑定
在这里插入图片描述

#include <sys/types.h>     
#include <sys/socket.h>
 
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数介绍:

  • sockfd:socket函数的返回值。
  • addr:通用结构体,包括协议家族,IP地址,端口号port
  • addrlen:addr的长度

绑定前我们要先定义一个 sockaddr_in 结构体填充所需绑定的数据,再传递进去。

(i)sockaddr_in结构体

在这里插入图片描述

struct sockaddr_in
  {
    short int sin_family;
    in_port_t sin_port;			
    struct in_addr sin_addr;	
 
    unsigned char sin_zero[8];
  };
  • sin_family:协议家族,表示通信类型(AF_INET网络通信,PF_INET本地通信)这个family被藏在SOCKADDR_COMMON 里面
  • sin_port:端口号(网络字节序)
  • sin_addr:IP地址。
  • sin_zero:填充字段,让sizeof(sockaddr_in) = 16
(ii)结构体初始化–bzero

创建结构体后要先清空数据(初始化),我们可以用 memset,系统也提供了接口:

在这里插入图片描述

struct sockaddr_in local;
bzero(&local, sizeof(local));
(iii)结构体数据填充-协议家族

变量sin_family协议家族位置:
我们需要填充的数据中有一个变量是sin_family,但是源码中并没直接见到,实际在__SOCKADDR_COMMON (sin_);中
在这里插入图片描述
这是一个冷门的用法:##,就是把双##两边的符号合并成一个新的符号
所以调用宏定义,把参数sin_传进去,sin_替换sa_prefix,sa_family_t sa_prefix##family就变成sa_family sin_family

数据填充: sin_family需要和socket套接字创建时的domain相同。

(iv)结构体数据填充–大小端转化问题

填充端口号:
填充端口号的时候要注意端口号是两个字节的数据,涉及到大小端问题。

换句话说就是由于端口号和IP将来是要发送到网络的,而网络数据流统一采用大端的形式,所以为了代码的可移植性,我们不管自己是大端还是小端,统一调用函数htons转化成大端。也就是把端口号从主机序列转化成网络序列(大端的形式)。

大小端转化接口:

#include <arpa/inet.h>
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

填充 IP 地址:
对于 IP,首先我们要先转成整数,再解决大小端问题。
换句话说就是对于IP来说,
第一步:如果直接发送的是点分十进制形式如(192.168.12.80)的形式,则需要占用过多的字节数,对网络传输无疑是一种很大的消耗,所以我们采用一个32位的整数来表示IP,也就是四个字节,在网络传输中,要将点分十进制的IP转化成整数,
第二步:把这四字节地址从主机序列转化成网络序列,进行传输。

系统给了直接能解决这两个问题的接口:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
int inet_aton(const char *cp, struct in_addr *inp);
 
in_addr_t inet_addr(const char *cp); //点分十进制转整数
 
in_addr_t inet_network(const char *cp);
 
char *inet_ntoa(struct in_addr in); //整数转点分十进制
 
struct in_addr inet_makeaddr(int net, int host);
 
in_addr_t inet_lnaof(struct in_addr in);
 
in_addr_t inet_netof(struct in_addr in);

这里重点学习两个,inet_addr(点分十进制转整数),现在使用的函数和inet_ntoa(整数转点分十进制),后续(4)使用的函数。

但是这里使用有个小问题:
在这里插入图片描述
c语言里结构体可以被直接整体初始化,但不能被直接整体赋值,也就是说要一个个赋值,因此我们右键查看定义,找到其中变量:
在这里插入图片描述
在这里插入图片描述
此时不会报错。

(v)设置进内核

文件+网络结合,才得到了一个绑定信息,到这里才使用bind

int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));

(3)启动服务器

作为一款网络服务器,是永远不退出的。

服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了

首先要知道服务器要死循环,永远不退出,除非用户删除。站在操作系统的角度,服务器是常驻内存中的进程,而我们启动服务器的时候要传递进去 IP 和端口号。

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
 
void Usage()
{
    std::cout << "Please enter:  ./UdpServer [ip] port" << std::endl;
}
 
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage();
        return 3;
    }
 
    std::string ip = argv[1];
    uint16_t port = atoi(argv[argc - 1]);
    std::unique_ptr<UdpServer> p(new UdpServer(port, ip));
    p->Start();
 
    return 0;
}

我们可以使用命令行参数的形式,在启动这个程序时就把IP和端口号传递过去。如上

(4)IP的绑定

(i)ifconfig

我们先来了解一个命令,ifconfig,操作系统中用于配置和显示网络接口参数的命令行工具。它主要用于查看和设置网络接口(如以太网和 Wi-Fi)的详细信息,包括 IP 地址、子网掩码、广播地址、MAC 地址等。

在这里插入图片描述

这里的 127.0.0.1 叫做 本地环回。client 和 server 发送数据只在本地协议栈中进行数据流动,不会将我们的数据发送到网络中。

作用:用来做本地网络服务器代码测试的,意思就是如果我们绑定的 IP 是 127.0.0.1 的话,在应用层发送的消息不会进入物理层,也就不会发送出去。

在这里插入图片描述

(ii)netstat

当我们运行起来后想要查看网络情况就可以用指令 netstat,后边也可以附带参数:

-a:显示所有连线中的 Socket。
-e:显示网络其他相关信息。
-i:显示网络界面信息表单。
-l:显示监控中的服务器的 Socket。
-n:直接使用 ip 地址(数字),而不通过域名服务器。
-p:显示正在使用 Socket 的程序识别码和程序名称。
-t:显示 TCP 传输协议的连线状况。
-u:显示 UDP 传输协议的连线状况。
在这里插入图片描述

我们可以看到确实能看到我们刚刚运行的程序成功bind了一个IP和端口号。

但是在实际情况下,服务器时不建议绑定一个固定IP的。

  • 安全性:攻击者可能会针对这个固定的IP地址进行攻击,比如DDoS攻击或者其他类型的网络攻击。相比之下,使用动态IP或者负载均衡等技术可以更好地分散攻击,提高系统的安全性。
  • 可用性:在某些情况下,固定IP可能并不总是可用的。例如,在云服务环境中,IP地址可能会因为服务器的迁移或重新部署而发生变化。如果服务端绑定到这样的固定IP,当IP地址发生变化时,服务端可能无法正常工作,导致服务中断。
  • 一个服务器可能会有多个IP(多张网卡),当客户端发送数据时,每张网卡都会收到该数据,如果我们想访问指定的某一个端口8080,并且如果指定了IP,那么只能由那一个指定的IP接受数据,但是如果我们绑定的是任意IP,那么只要是发送给8080端口的,任意一个网卡接受到了都会向上交付给服务器。

在这里插入图片描述

所以我们修改一下,如果创建服务端的时候,传了IP,那就用传的IP,如果没用,那就用我们INADDR_ANY其实就是(0.0.0.0),

在这里插入图片描述
INADDR_ANY 如上所示,任何地址都接受。

绑定之后,只要是发送到这台主机上,端口号为XXX(我这里是8080)的,就将数据全部交给这个进程。

 addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());

(5)读取数据 recvfrom

#include <sys/types.h>
#include <sys/socket.h>
 
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数介绍:

  • sockfd:从哪个套接字读。
  • buf:数据放入的缓冲区。
  • len:缓冲区长度。
  • flags:读取方式。 0 代表阻塞式读取。
  • src_addr 和 addrlen:输出型参数,返回对应的消息内容是从哪一个客户端发出的。第一个是自己定义的结构体,第二个是结构体长度。
void Start()
    {
         while (true)
        {
            char temp[1024];
            sockaddr_in addr;
            socklen_t addrlen = sizeof(addr);
            int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
            std::string ip = inet_ntoa(addr.sin_addr);
            uint16_t port = ntohs(addr.sin_port);
            if (n > 0)
            {
                printf("[%s:%d]# %s", ip.c_str(), port, temp);
            }
        }
    }
(i)inet_ntoa函数

recvfrom里的参数 src_addr用来保存发送端的信息,我们在前面使用bind函数的时候,也介绍过sockaddr结构,里面包含了发送端的IP和端口port,但是因为是网络序列,我们需要将他转化回主机序列,ip是32位整数,我们想转化成点分十进制形式方便观看,于是可以使用inet_ntoa函数。

char *inet_ntoa(struct in_addr in);

inet_ntoa 这个函数返回了一个 char*,很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果。那么是否需要调用者手动释放呢?
在这里插入图片描述
man 手册上说,inet_ntoa 函数是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。

那如果我们调用多次这个函数,会有什么样的效果呢?
在这里插入图片描述
在这里插入图片描述
因为 inet_ntoa 把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果。

如果有多个线程调用 inet_ntoa,是否会出现异常情况呢?

  • 在 APUE 中,明确提出 inet_ntoa 不是线程安全的函数。
  • 但是在 centos7 上测试并没有出现问题,可能内部的实现加了互斥锁。
  • 在多线程环境下,推荐使用 inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。

(6)服务端整体代码

#pragma once
 
#include <iostream>
#include <string>
#include <unordered_set>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
class UdpServer
{
public:
    UdpServer(const uint16_t &port, const std::string &str = "")
    {
        // 创建套接字
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            std::cerr << "create socket error" << std::endl;
            exit(1);
        }
        std::cout << "create socket success, socket: " << _socket << std::endl;
 
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());
 
        int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));
        if (n < 0)
        {
            std::cout << "bind error" << std::endl;
            exit(2);
        }
        std::cout << "bind success " << std::endl;
    }
 
    void Start()
    {
         while (true)
        {
            char temp[1024];
            sockaddr_in addr;
            socklen_t addrlen = sizeof(addr);
            int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
            std::string ip = inet_ntoa(addr.sin_addr);
            uint16_t port = ntohs(addr.sin_port);
            if (n > 0)
            {
                printf("[%s:%d]# %s", ip.c_str(), port, temp);
            }
        }
    }
 
    ~UdpServer()
    {
        close(_socket);
    }
 
private:
    int _socket;
};

2、客户端实现

服务端的创建和客户端类似。我们也需要创建套接字

(1)创建套接字

void Usage()
{
    std::cout << "Please enter:  ./Client [ip] port" << std::endl;
}
 
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        return 3;
    }
 
    std::string ip = argv[1];
    uint16_t port = atoi(argv[argc - 1]);
    // 创建套接字
    int clientsocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientsocket < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }
    std::cout << "create socket success, socket: " << clientsocket << std::endl;
 
    return 0;
}

(2)绑定问题

客户端必须绑定IP和端口,但是不用显示的bind。

这句话是什么意思呢,就是我们需要将网卡文件和IP+端口进行绑定,但是不需要我们自己手动绑定,操作系统会自动帮我们绑定一个端口号。

为什么不能显示绑定呢,首先我们要知道,一个端口只能对应一个进程,现在有两家公司,各推出了一款客户端,如果他们想让端口不冲突,那就不能让自己家的客户端绑定的端口和对方的一样,如果一样就会起冲突,但是互联网上有很多家公司,难道都要协商一下,哪个端口谁来用吗。所以我们采用了让操作系统帮我们自动分配一个没有使用的端口号。

当我们调用了类似与sendto(发送信息)的函数的时候,操作系统会帮我们自动分配一个端口号,也就是说,客户端每次启动时的端口号可能都不相同。

服务端的端口号为什么是固定的?

服务端的端口号是众所周知的,如果每次都不一样的话,客户端就找不到服务器了

(3)发送信息sendto

#include <sys/types.h>
#include <sys/socket.h>
 
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数介绍:

  • sockfd:从哪个套接字中读取
  • buf:将缓冲区中的数据发给对端
  • len:要发送多少个字节
  • flags:写入方式,0表示阻塞写入
  • src_addr:对端主机的信息(包括协议家族,IP,port)
  • addrlen:表示src_addr的长度

参数和recvfrom类似

// 直接给服务器send,系统会自动帮我们bind
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    while (true)
    {
        std::string buffer;
        std::cout << "Please enter# ";
        std::getline(std::cin, buffer);
        sendto(clientsocket, buffer.c_str(), buffer.size(), 0, (sockaddr *)&addr, sizeof(addr));
    }

当我们将客户端发送消息的逻辑 完成之后就可以正常开始通信了。

3、源码简易版

(1)UdpServer.hpp

//Server.hpp
 
#pragma once
 
#include <iostream>
#include <string>
#include <unordered_set>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
class UdpServer
{
public:
    UdpServer(const uint16_t &port, const std::string &str = "")
    {
        // 创建套接字
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            std::cerr << "create socket error" << std::endl;
            exit(1);
        }
        std::cout << "create socket success, socket: " << _socket << std::endl;
 
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = str.empty() ? INADDR_ANY : inet_addr(str.c_str());
 
        int n = bind(_socket, (sockaddr *)&addr, sizeof(addr));
        if (n < 0)
        {
            std::cout << "bind error" << std::endl;
            exit(2);
        }
        std::cout << "bind success " << std::endl;
    }
 
    void Start()
    {
        while (true)
        {
            char temp[1024];
            sockaddr_in addr;
            socklen_t addrlen = sizeof(addr);
 
            int n = recvfrom(_socket, temp, sizeof(temp) - 1, 0, (sockaddr *)&addr, &addrlen);
            std::string ip = inet_ntoa(addr.sin_addr);
            uint16_t port = ntohs(addr.sin_port);
            if (n > 0)
            {
                temp[n] = 0;
                printf("[%s:%d]# %s\n", ip.c_str(), port, temp);
            }
        }
    }
 
    ~UdpServer()
    {
        close(_socket);
    }
 
private:
    int _socket;
}; 

(2)Server.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
 
void Usage()
{
    std::cout << "Please enter:  ./UdpServer [ip] port" << std::endl;
}
 
int main(int argc, char* argv[])
{
    if (argc != 3 && argc != 2)
    {
        Usage();
        return 3;
    }
 
    std::string ip = argv[1];
    uint16_t port = atoi(argv[argc - 1]);
    if (argc == 2)
        ip = "";
    std::unique_ptr<UdpServer> p(new UdpServer(port, ip));
    p->Start();
 
    return 0;
}

(3)Client.cc

#include <iostream>
#include <memory>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
void Usage()
{
    std::cout << "Please enter:  ./Client [ip] port" << std::endl;
}
 
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage();
        return 3;
    }
 
    std::string ip = argv[1];
    uint16_t port = atoi(argv[argc - 1]);
    // 创建套接字
    int clientsocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientsocket < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }
    std::cout << "create socket success, socket: " << clientsocket << std::endl;
 
    // 直接给服务器send,系统会自动帮我们bind
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    while (true)
    {
        std::string buffer;
        std::cout << "Please enter# ";
        std::getline(std::cin, buffer);
        sendto(clientsocket, buffer.c_str(), buffer.size(), 0, (sockaddr *)&addr, sizeof(addr));
    }
 
 
    return 0;
}

4、源码封装版

对相关代码封装,增加了日志

(1)UdpServer.hpp

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

class UdpServer
{
public:
    UdpServer(uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false)
    {
    }
    // 都是套路
    void InitServer()
    {
        // 1. 创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;

        // 2. 填充网络信息,并bind绑定
        // 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
        // struct sockaddr_in local;
        // bzero(&local, sizeof(local));
        // local.sin_family = AF_INET;
        // local.sin_port = ::htons(_port);                  // 要被发送给对方的,即要发到网络中!
        // //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
        //local.sin_addr.s_addr = INADDR_ANY;

        // 2.2 bind : 设置进入内核中
        int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success";
    }
    void Start()
    {
        _isrunning = true;
        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定

            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
            if (n > 0)
            {
                // 1. 消息内容 && 2. 谁发给我的
                // uint16_t clientport = ::ntohs(peer.sin_port);
                // std::string clientip = ::inet_ntoa(peer.sin_addr);

                InetAddr cli(peer);
                inbuffer[n] = 0;

                std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;

                LOG(LogLevel::DEBUG) << clientinfo;

                std::string echo_string = "echo# ";
                echo_string += inbuffer;

                ::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        if(_sockfd > gsockfd)
            ::close(_sockfd);
    }

private:
    int _sockfd;
    InetAddr _addr;
    // uint16_t _port;  // 服务器未来的端口号
    // // std::string _ip; // 服务器所对应的IP
    bool _isrunning; // 服务器运行状态
};

#endif

(2)UdpServerMain.cc

#include "UdpServer.hpp"

// ./server_udp localport
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(USAGE_ERR);
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    ENABLE_CONSOLE_LOG();

    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
    svr_uptr->InitServer();
    svr_uptr->Start();

    return 0;
}

(3)UdpClient.hpp

#pragma once

(4)UdpClientMain.cc

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


// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        Die(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

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

    // 1.1 填充server信息
    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());

    // 2. clientdone
    while(true)
    {
        std::cout << "Please Enter# ";
        std::string message;
        std::getline(std::cin, message);
        // client 不需要bind吗?socket <-> socket
        // client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
        // 而是,客户端首次sendto消息的时候,由OS自动进行bind
        // 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
        // 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
        int n = ::sendto(sockfd, message.c_str(),message.size(), 0, CONV(&server), sizeof(server));
        (void)n;

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return 0;
}

(5)Mutex.hpp

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

namespace LockModule
{
    class Mutex
    {
    public:
        Mutex(const Mutex&) = delete;
        const Mutex& operator = (const Mutex&) = delete;
        Mutex()
        {
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n;
        }
        ~Mutex()
        {
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n;
        }
        void Lock()
        {
            int n = ::pthread_mutex_lock(&_lock);
            (void)n;
        }
        pthread_mutex_t *LockPtr()
        {
            return &_lock;
        }
        void Unlock()
        {
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n;
        }

    private:
        pthread_mutex_t _lock;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mtx):_mtx(mtx)
        {
            _mtx.Lock();
        }
        ~LockGuard()
        {
            _mtx.Unlock();
        }
    private:
        Mutex &_mtx;
    };
}

(6)Makefile

.PHONY:all
all:server_udp client_udp

server_udp:UdpServerMain.cc
	g++ -o $@ $^ -std=c++17
client_udp:UdpClientMain.cc
	g++ -o $@ $^ -std=c++17

.PHONY:clean
clean:
	rm -f server_udp client_udp

(7)Log.hpp

#pragma once

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

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);

        return buffer;
    }

    // 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
    //  1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }

    // 3. 刷新策略.
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        ~ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }

    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(logpath),
              _logname(logname)
        {
            // 确认_logpath是存在的.
            LockGuard lockguard(_lock);

            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        ~FileLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::string log = _logpath + _logname; // ./log/log.txt
            std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }

    private:
        std::string _logpath;
        std::string _logname;

        // 锁
        Mutex _lock;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger() {}
        // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                : _currtime(CurrentTime()),
                  _level(level),
                  _pid(::getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

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

        private:
            std::string _currtime; // 当前日志的时间
            LogLevel _level;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _filename; // 源文件名称
            int _line;             // 日志所在的行号
            Logger &_logger;       // 负责根据不同的策略进行刷新
            std::string _loginfo;  // 一条完整的日志记录
        };

        // 就是要拷贝,故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

    private:
        std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

(8)InetAddr.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"

class InetAddr
{
private:
    void PortNet2Host()
    {
        _port = ::ntohs(_net_addr.sin_port);
    }
    void IpNet2Host()
    {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        (void)ip;
    }

public:
    InetAddr()
    {
    }
    InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
    {
        PortNet2Host();
        IpNet2Host();
    }
    InetAddr(uint16_t port) : _port(port), _ip("")
    {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = INADDR_ANY;
    }
    struct sockaddr *NetAddr() { return CONV(&_net_addr); }
    socklen_t NetAddrLen() { return sizeof(_net_addr); }
    std::string Ip() { return _ip; }
    uint16_t Port() { return _port; }
    ~InetAddr()
    {
    }

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

(9)Common.hpp

#pragma once

#include <iostream>


#define Die(code)   \
    do              \
    {               \
        exit(code); \
    } while (0)

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

enum 
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

5、加餐–网络命令

(1)Ping命令

用来检测两个主机能不能通信。用ping检测是否是网络没联通,要是能ping通,就证明是我们本身服务器写的有问题。
在这里插入图片描述
ping -c x 就ping x 次

(2)pidof

在查看服务器的进程id 时非常方便
语法:pidof[进程名]
功能:通过进程名,查看进程 id

也可以搭配管道组合使用
在这里插入图片描述

二、V2 版本-DictServer

1、UdpServer.hpp

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <cstring>
#include <cerrno>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"

using namespace LogModule;

const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

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

class UdpServer
{
public:
    UdpServer(func_t func, uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false),
          _func(func)
    {
    }
    // 都是套路
    void InitServer()
    {
        // 1. 创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;

        // 2. 填充网络信息,并bind绑定
        // 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
        // struct sockaddr_in local;
        // bzero(&local, sizeof(local));
        // local.sin_family = AF_INET;
        // local.sin_port = ::htons(_port);                  // 要被发送给对方的,即要发到网络中!
        // //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
        // local.sin_addr.s_addr = INADDR_ANY;

        // 2.2 bind : 设置进入内核中
        int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success";
    }
    void Start()
    {
        _isrunning = true;
        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定

            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
            if (n > 0)
            {
                // 1. 消息内容 && 2. 谁发给我的
                // uint16_t clientport = ::ntohs(peer.sin_port);
                // std::string clientip = ::inet_ntoa(peer.sin_addr);
                // InetAddr cli(peer);
                // inbuffer[n] = 0;
                // std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;
                // LOG(LogLevel::DEBUG) << clientinfo;
                // std::string echo_string = "echo# ";
                // echo_string += inbuffer;
                // 把英文单词转化成为中文
                inbuffer[n] = 0;
                std::string result = _func(inbuffer); // 这个是回调,出去,还会回来!!!!
                ::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), sizeof(peer));
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        if(_sockfd > gsockfd)
            ::close(_sockfd);
    }

private:
    int _sockfd;
    InetAddr _addr;
    // uint16_t _port;  // 服务器未来的端口号
    // // std::string _ip; // 服务器所对应的IP
    bool _isrunning; // 服务器运行状态

    // 业务,回调
    func_t _func;
};

#endif

2、UdpServerMain.cc

#include "UdpServer.hpp"
#include "Dictionary.hpp"

// ./server_udp localport
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(USAGE_ERR);
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    ENABLE_CONSOLE_LOG();
    std::shared_ptr<Dictionary> dict_sptr = std::make_shared<Dictionary>();

    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&dict_sptr](const std::string &word){
        std::cout << "|" << word << "|" << std::endl;
        return dict_sptr->Translate(word);
    }, port);

    // std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(std::bind(&Dictionary::Translate,\
    //             dict_sptr.get(), std::placeholders::_1), port);

    svr_uptr->InitServer();
    svr_uptr->Start();

    return 0;
}

3、UdpClient.hpp

#pragma once

4、UdpClientMain.cc

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


// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        Die(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

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

    // 1.1 填充server信息
    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());

    // 2. clientdone
    while(true)
    {
        std::cout << "Please Enter# ";
        std::string message;
        std::getline(std::cin, message);
        // client 不需要bind吗?socket <-> socket
        // client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
        // 而是,客户端首次sendto消息的时候,由OS自动进行bind
        // 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
        // 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
        int n = ::sendto(sockfd, message.c_str(),message.size(), 0, CONV(&server), sizeof(server));
        (void)n;

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return 0;
}

5、Mutex.hpp

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

namespace LockModule
{
    class Mutex
    {
    public:
        Mutex(const Mutex&) = delete;
        const Mutex& operator = (const Mutex&) = delete;
        Mutex()
        {
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n;
        }
        ~Mutex()
        {
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n;
        }
        void Lock()
        {
            int n = ::pthread_mutex_lock(&_lock);
            (void)n;
        }
        pthread_mutex_t *LockPtr()
        {
            return &_lock;
        }
        void Unlock()
        {
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n;
        }

    private:
        pthread_mutex_t _lock;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mtx):_mtx(mtx)
        {
            _mtx.Lock();
        }
        ~LockGuard()
        {
            _mtx.Unlock();
        }
    private:
        Mutex &_mtx;
    };
}

6、Makefile

.PHONY:all
all:server_udp client_udp

server_udp:UdpServerMain.cc
	g++ -o $@ $^ -std=c++17
client_udp:UdpClientMain.cc
	g++ -o $@ $^ -std=c++17

.PHONY:clean
clean:
	rm -f server_udp client_udp

7、Log.hpp

#pragma once

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

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);

        return buffer;
    }

    // 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
    //  1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }

    // 3. 刷新策略.
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        ~ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }

    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(logpath),
              _logname(logname)
        {
            // 确认_logpath是存在的.
            LockGuard lockguard(_lock);

            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        ~FileLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::string log = _logpath + _logname; // ./log/log.txt
            std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }

    private:
        std::string _logpath;
        std::string _logname;

        // 锁
        Mutex _lock;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger() {}
        // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                : _currtime(CurrentTime()),
                  _level(level),
                  _pid(::getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

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

        private:
            std::string _currtime; // 当前日志的时间
            LogLevel _level;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _filename; // 源文件名称
            int _line;             // 日志所在的行号
            Logger &_logger;       // 负责根据不同的策略进行刷新
            std::string _loginfo;  // 一条完整的日志记录
        };

        // 就是要拷贝,故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

    private:
        std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

8、InetAddr.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"

class InetAddr
{
private:
    void PortNet2Host()
    {
        _port = ::ntohs(_net_addr.sin_port);
    }
    void IpNet2Host()
    {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        (void)ip;
    }

public:
    InetAddr()
    {
    }
    InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
    {
        PortNet2Host();
        IpNet2Host();
    }
    InetAddr(uint16_t port) : _port(port), _ip("")
    {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = INADDR_ANY;
    }
    struct sockaddr *NetAddr() { return CONV(&_net_addr); }
    socklen_t NetAddrLen() { return sizeof(_net_addr); }
    std::string Ip() { return _ip; }
    uint16_t Port() { return _port; }
    ~InetAddr()
    {
    }

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

9、Dictionary.hpp

#pragma once

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

const std::string gpath = "./";
const std::string gdictname = "dict.txt";
const std::string gsep = ": ";

using namespace LogModule;

class Dictionary
{
private:
    bool LoadDictionary()
    {
        std::string file = _path + _filename;
        std::ifstream in(file.c_str());
        if (!in.is_open())
        {
            LOG(LogLevel::ERROR) << "open file " << file << " error";
            return false;
        }

        std::string line;
        while (std::getline(in, line))
        {
            // happy: 快乐的
            std::string key;
            std::string value;
            if (SplitString(line, &key, &value, gsep))
            { // line -> key, value
                _dictionary.insert(std::make_pair(key, value));
            }
        }

        in.close();
        return true;
    }

public:
    Dictionary(const std::string &path = gpath, const std::string &filename = gdictname)
        : _path(path),
          _filename(filename)
    {
        LoadDictionary();
        Print();
    }
    std::string Translate(const std::string &word)
    {
        auto iter = _dictionary.find(word);
        if(iter == _dictionary.end()) return "None";
        return iter->second;
    }
    void Print()
    {
        for(auto &item : _dictionary)
        {
            std::cout << item.first << ":" << item.second<< std::endl;
        }
    }
    ~Dictionary()
    {
    }

private:
    std::unordered_map<std::string, std::string> _dictionary;
    std::string _path;
    std::string _filename;
};

10、Common.hpp

#pragma once

#include <iostream>
#include <string>


#define Die(code)   \
    do              \
    {               \
        exit(code); \
    } while (0)

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

enum 
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

// happy: 快乐的
bool SplitString(const std::string &line, std::string *key, std::string *value, const std::string &sep)
{
    auto pos = line.find(sep);
    if(pos == std::string::npos) return false;
    *key = line.substr(0, pos);
    *value = line.substr(pos+sep.size());
    if(key->empty() || value->empty()) return false;
    return true;
}

11、dict.txt

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

三、V3 版本-ChatServer

1、UdpServer.hpp

#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__

#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule;

const static int gsockfd = -1;
// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机
const static uint16_t gdefaultport = 8080;

using adduser_t = std::function<void(InetAddr &id)>;
using remove_t = std::function<void(InetAddr &id)>;

using task_t = std::function<void()>;
using route_t = std::function<void(int sockfd, const std::string &message)>;

class nocopy
{
public:
    nocopy(){}
    nocopy(const nocopy &) = delete;
    const nocopy& operator = (const nocopy &) = delete;
    ~nocopy(){}
};

class UdpServer : public nocopy
{
public:
    UdpServer(uint16_t port = gdefaultport)
        : _sockfd(gsockfd),
          _addr(port),
          _isrunning(false)
    {
    }
    // 都是套路
    void InitServer()
    {
        // 1. 创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地?
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd;

        // 2. 填充网络信息,并bind绑定
        // 2.1 有没有把socket信息,设置进入内核中??没有,只是填充了结构体!
        // struct sockaddr_in local;
        // bzero(&local, sizeof(local));
        // local.sin_family = AF_INET;
        // local.sin_port = ::htons(_port);                  // 要被发送给对方的,即要发到网络中!
        // //local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip->4bytes 2. network order //TODO
        // local.sin_addr.s_addr = INADDR_ANY;

        // 2.2 bind : 设置进入内核中
        int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind: " << strerror(errno);
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success";
    }
    void RegisterService(adduser_t adduser, route_t route, remove_t removeuser)
    {
        _adduser = adduser;
        _route = route;
        _removeuser = removeuser;
    }
    void Start()
    {
        _isrunning = true;
        while (true)
        {
            char inbuffer[1024]; // string
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须设定

            ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len);
            if (n > 0)
            {
                // 1. 消息内容 && 2. 谁发给我的
                // uint16_t clientport = ::ntohs(peer.sin_port);
                // std::string clientip = ::inet_ntoa(peer.sin_addr);

                InetAddr cli(peer);
                inbuffer[n] = 0;
                std::string message;
                if (strcmp(inbuffer, "QUIT") == 0)
                {
                    // 移除观察者
                    _removeuser(cli);
                    message = cli.Addr() + "# " + "我走了,你们聊!";
                }
                else
                {
                    // 2. 新增用户
                    _adduser(cli);
                    message = cli.Addr() + "# " + inbuffer;
                }

                // 3. 构建转发任务,推送给线程池,让线程池进行转发
                task_t task = std::bind(UdpServer::_route, _sockfd, message);
                ThreadPool<task_t>::getInstance()->Equeue(task);

                // std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + " # " + inbuffer;

                // LOG(LogLevel::DEBUG) << clientinfo;

                // std::string echo_string = "echo# ";
                // echo_string += inbuffer;

                // ::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer));
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        if (_sockfd > gsockfd)
            ::close(_sockfd);
    }

private:
    int _sockfd;
    InetAddr _addr;
    bool _isrunning; // 服务器运行状态

    // 新增用户
    adduser_t _adduser;
    // 移除用户
    remove_t _removeuser;
    // 数据转发
    route_t _route;
};

#endif

2、UdpServerMain.cc

#include "UdpServer.hpp"
#include "User.hpp"

// ./server_udp localport
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " localport" << std::endl;
        Die(USAGE_ERR);
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    ENABLE_CONSOLE_LOG();

    // 用户管理模块
    std::shared_ptr<UserManager> um = std::make_shared<UserManager>();
    // 网络模块
    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
    svr_uptr->RegisterService([&um](InetAddr &id){ um->AddUser(id); },
                              [&um](int sockfd, const std::string &message){ um->Router(sockfd, message);},
                              [&um](InetAddr &id){ um->DelUser(id);}
                              );
    svr_uptr->InitServer();

    svr_uptr->Start();

    return 0;
}

3、UdpClient.hpp

#pragma once

4、UdpClientMain.cc

#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>

int sockfd = -1;
struct sockaddr_in server;

void ClientQuit(int signo)
{
    (void)signo;
    const std::string quit = "QUIT";
    int n = ::sendto(sockfd, quit.c_str(), quit.size(), 0, CONV(&server), sizeof(server));
    exit(0);
}

void *Recver(void *args)
{
    while (true)
    {
        (void)args;
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buffer[1024];
        int n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cerr << buffer << std::endl; // 代码没问题,重定向也没问题,管道读写同时打开,才会继续向后运行
            // fprintf(stderr, "%s\n", buffer);
            // fflush(stderr);
        }
    }
}

// CS
// ./client_udp serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        Die(USAGE_ERR);
    }
    signal(2, ClientQuit);
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

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

    // 1.1 填充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());

    pthread_t tid;
    pthread_create(&tid, nullptr, Recver, nullptr);

    // 1.2 启动的时候,给服务器推送消息即可
    const std::string online = " ... 来了哈!";
    int n = ::sendto(sockfd, online.c_str(), online.size(), 0, CONV(&server), sizeof(server));

    // 2. clientdone
    while (true)
    {
        std::cout << "Please Enter# ";
        std::string message;
        std::getline(std::cin, message);
        // client 不需要bind吗?socket <-> socket
        // client必须也要有自己的ip和端口!但是客户端,不需要自己显示的调用bind!!
        // 而是,客户端首次sendto消息的时候,由OS自动进行bind
        // 1. 如何理解client自动随机bind端口号? 一个端口号,只能被一个进程bind
        // 2. 如何理解server要显示的bind?服务器的端口号,必须稳定!!必须是众所周知且不能改变轻易改变的!
        int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));
        (void)n;
    }

    return 0;
}

5、Mutex.hpp

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

namespace LockModule
{
    class Mutex
    {
    public:
        Mutex(const Mutex&) = delete;
        const Mutex& operator = (const Mutex&) = delete;
        Mutex()
        {
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n;
        }
        ~Mutex()
        {
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n;
        }
        void Lock()
        {
            int n = ::pthread_mutex_lock(&_lock);
            (void)n;
        }
        pthread_mutex_t *LockPtr()
        {
            return &_lock;
        }
        void Unlock()
        {
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n;
        }

    private:
        pthread_mutex_t _lock;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mtx):_mtx(mtx)
        {
            _mtx.Lock();
        }
        ~LockGuard()
        {
            _mtx.Unlock();
        }
    private:
        Mutex &_mtx;
    };
}

6、ThreadPool.hpp

#pragma once

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

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

    // 用来做测试的线程方法
    void DefaultTest()
    {
        while (true)
        {
            LOG(LogLevel::DEBUG) << "我是一个测试方法";
            sleep(1);
        }
    }

    using thread_t = std::shared_ptr<Thread>;

    const static int defaultnum = 5;

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

        void HandlerTask(std::string name)
        {
            LOG(LogLevel::INFO) << "线程: " << name << ", 进入HandlerTask的逻辑";
            while (true)
            {
                // 1. 拿任务
                T t;
                {
                    LockGuard lockguard(_lock);
                    while (IsEmpty() && _isrunning)
                    {
                        _wait_num++;
                        _cond.Wait(_lock);
                        _wait_num--;
                    }
                    // 2. 任务队列为空 && 线程池退出了
                    if (IsEmpty() && !_isrunning)
                        break;

                    t = _taskq.front();
                    _taskq.pop();
                }

                // 2. 处理任务
                t(); // 规定,未来所有的任务处理,全部都是必须提供()方法!
            }
            LOG(LogLevel::INFO) << "线程: " << name << " 退出";
        }
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

        ThreadPool(int num = defaultnum) : _num(num), _wait_num(0), _isrunning(false)
        {
            for (int i = 0; i < _num; i++)
            {
                _threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1)));
                LOG(LogLevel::INFO) << "构建线程" << _threads.back()->Name() << "对象 ... 成功";
            }
        }

    public:
        static ThreadPool<T> *getInstance()
        {
            if (instance == NULL)
            {
                LockGuard lockguard(mutex);
                if (instance == NULL)
                {
                    LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";
                    instance = new ThreadPool<T>();
                    instance->Start();
                }
            }

            return instance;
        }

        void Equeue(T &in)
        {
            LockGuard lockguard(_lock);
            if (!_isrunning)
                return;
            // _taskq.push(std::move(in));
            _taskq.push(in);
            if (_wait_num > 0)
                _cond.Notify();
        }
        void Start()
        {
            if (_isrunning)
                return;
            _isrunning = true; // bug fix??
            for (auto &thread_ptr : _threads)
            {
                LOG(LogLevel::INFO) << "启动线程" << thread_ptr->Name() << " ... 成功";
                thread_ptr->Start();
            }
        }
        void Wait()
        {
            for (auto &thread_ptr : _threads)
            {
                thread_ptr->Join();
                LOG(LogLevel::INFO) << "回收线程" << thread_ptr->Name() << " ... 成功";
            }
        }
        void Stop()
        {
            LockGuard lockguard(_lock);
            if (_isrunning)
            {
                // 3. 不能在入任务了
                _isrunning = false; // 不工作
                // 1. 让线程自己退出(要唤醒) && // 2. 历史的任务被处理完了
                if (_wait_num > 0)
                    _cond.NotifyAll();
            }
        }
        ~ThreadPool()
        {
        }

    private:
        std::vector<thread_t> _threads;
        int _num;
        int _wait_num;
        std::queue<T> _taskq; // 临界资源

        Mutex _lock;
        Cond _cond;

        bool _isrunning;

        static ThreadPool<T> *instance;
        static Mutex mutex; // 只用来保护单例
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::instance = NULL;
    template <typename T>
    Mutex ThreadPool<T>::mutex; // 只用来保护单例
}

7、Thread.hpp

#ifndef _THREAD_HPP__
#define _THREAD_HPP__

#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>

// v1
namespace ThreadModule
{
    using func_t = std::function<void(std::string name)>;
    static int number = 1;
    enum class TSTATUS
    {
        NEW,
        RUNNING,
        STOP
    };

    class Thread
    {
    private:
        // 成员方法!
        static void *Routine(void *args)
        {
            Thread *t = static_cast<Thread *>(args);
            t->_status = TSTATUS::RUNNING;
            t->_func(t->Name());
            return nullptr;
        }
        void EnableDetach() { _joinable = false; }

    public:
        Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true)
        {
            _name = "Thread-" + std::to_string(number++);
            _pid = getpid();
        }
        bool Start()
        {
            if (_status != TSTATUS::RUNNING)
            {
                int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
                if (n != 0)
                    return false;
                return true;
            }
            return false;
        }
        bool Stop()
        {
            if (_status == TSTATUS::RUNNING)
            {
                int n = ::pthread_cancel(_tid);
                if (n != 0)
                    return false;
                _status = TSTATUS::STOP;
                return true;
            }
            return false;
        }
        bool Join()
        {
            if (_joinable)
            {
                int n = ::pthread_join(_tid, nullptr);
                if (n != 0)
                    return false;
                _status = TSTATUS::STOP;
                return true;
            }
            return false;
        }
        void Detach()
        {
            EnableDetach();
            pthread_detach(_tid);
        }
        bool IsJoinable() { return _joinable; }
        std::string Name() {return _name;}
        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        pid_t _pid;
        bool _joinable; // 是否是分离的,默认不是
        func_t _func;
        TSTATUS _status;
    };
}

// v2
// namespace ThreadModule
// {
//     static int number = 1;
//     enum class TSTATUS
//     {
//         NEW,
//         RUNNING,
//         STOP
//     };

//     template <typename T>
//     class Thread
//     {
//         using func_t = std::function<void(T)>;
//     private:
//         // 成员方法!
//         static void *Routine(void *args)
//         {
//             Thread<T> *t = static_cast<Thread<T> *>(args);
//             t->_status = TSTATUS::RUNNING;
//             t->_func(t->_data);
//             return nullptr;
//         }
//         void EnableDetach() { _joinable = false; }

//     public:
//         Thread(func_t func, T data) : _func(func), _data(data), _status(TSTATUS::NEW), _joinable(true)
//         {
//             _name = "Thread-" + std::to_string(number++);
//             _pid = getpid();
//         }
//         bool Start()
//         {
//             if (_status != TSTATUS::RUNNING)
//             {
//                 int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
//                 if (n != 0)
//                     return false;
//                 return true;
//             }
//             return false;
//         }
//         bool Stop()
//         {
//             if (_status == TSTATUS::RUNNING)
//             {
//                 int n = ::pthread_cancel(_tid);
//                 if (n != 0)
//                     return false;
//                 _status = TSTATUS::STOP;
//                 return true;
//             }
//             return false;
//         }
//         bool Join()
//         {
//             if (_joinable)
//             {
//                 int n = ::pthread_join(_tid, nullptr);
//                 if (n != 0)
//                     return false;
//                 _status = TSTATUS::STOP;
//                 return true;
//             }
//             return false;
//         }
//         void Detach()
//         {
//             EnableDetach();
//             pthread_detach(_tid);
//         }
//         bool IsJoinable() { return _joinable; }
//         std::string Name() { return _name; }
//         ~Thread()
//         {
//         }

//     private:
//         std::string _name;
//         pthread_t _tid;
//         pid_t _pid;
//         bool _joinable; // 是否是分离的,默认不是
//         func_t _func;
//         TSTATUS _status;
//         T _data;
//     };
// }

#endif

8、User.hpp

#pragma once

#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <algorithm>
#include <sys/types.h>
#include <sys/socket.h>

#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"

using namespace LogModule;
using namespace LockModule;

class UserInterface
{
public:
    virtual ~UserInterface() = default;
    virtual void SendTo(int sockfd, const std::string &message) = 0;
    virtual bool operator==(const InetAddr &u) = 0;
    virtual std::string Id() = 0;
};

class User : public UserInterface
{
public:
    User(const InetAddr &id) : _id(id)
    {
    }
    void SendTo(int sockfd, const std::string &message) override
    {
        LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info: " << message;
        int n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen());
        (void)n;
    }
    bool operator==(const InetAddr &u) override
    {
        return _id == u;
    }
    std::string Id() override
    {
        return _id.Addr();
    }
    ~User()
    {
    }

private:
    InetAddr _id;
};

// 对用户消息进行路由
//  UserManager 把所有的用户先管理起来!
//  观察者模式!observer
class UserManager
{
public:
    UserManager()
    {
    }
    void AddUser(InetAddr &id)
    {
        LockGuard lockguard(_mutex);
        for (auto &user : _online_user)
        {
            if (*user == id)
            {
                LOG(LogLevel::INFO) << id.Addr() << "用户已经存在";
                return;
            }
        }
        LOG(LogLevel::INFO) << " 新增该用户: " <<  id.Addr();
        _online_user.push_back(std::make_shared<User>(id));
        PrintUser();
    }
    void DelUser(InetAddr &id)
    {
        // v1
        auto pos = std::remove_if(_online_user.begin(), _online_user.end(), [&id](std::shared_ptr<UserInterface> &user){
            return *user == id;
        });
        _online_user.erase(pos, _online_user.end());
        PrintUser();

        //v2
        // for(auto user : _online_user)
        // {
        //     if(*user == id)
        //     {
        //         _online_user.erase(user);
        //         break; // 迭代器失效的问题
        //     }
        // }
    }
    void Router(int sockfd, const std::string &message)
    {
        LockGuard lockguard(_mutex);
        for (auto &user : _online_user)
        {
            user->SendTo(sockfd, message);
        }
    }
    void PrintUser()
    {
        for(auto user : _online_user)
        {
            LOG(LogLevel::DEBUG) <<"在线用户-> "<<  user->Id();
        }
    }
    ~UserManager()
    {
    }

private:
    std::list<std::shared_ptr<UserInterface>> _online_user;
    Mutex _mutex;
};

9、Makefile

.PHONY:all
all:server_udp client_udp

server_udp:UdpServerMain.cc
	g++ -o $@ $^ -std=c++17 -lpthread
client_udp:UdpClientMain.cc
	g++ -o $@ $^ -std=c++17 -lpthread

.PHONY:clean
clean:
	rm -f server_udp client_udp

10、Log.hpp

#pragma once

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

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);

        return buffer;
    }

    // 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)
    //  1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }

    // 3. 刷新策略.
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        ~ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }

    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            : _logpath(logpath),
              _logname(logname)
        {
            // 确认_logpath是存在的.
            LockGuard lockguard(_lock);

            if (std::filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }
        ~FileLogStrategy()
        {
        }
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::string log = _logpath + _logname; // ./log/log.txt
            std::ofstream out(log, std::ios::app); // 日志写入,一定是追加
            if (!out.is_open())
            {
                return;
            }
            out << message << "\n";
            out.close();
        }

    private:
        std::string _logpath;
        std::string _logname;

        // 锁
        Mutex _lock;
    };

    // 日志类: 构建日志字符串, 根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger() {}
        // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                : _currtime(CurrentTime()),
                  _level(level),
                  _pid(::getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] "
                         << "[" << Level2String(_level) << "] "
                         << "[" << _pid << "] "
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

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

        private:
            std::string _currtime; // 当前日志的时间
            LogLevel _level;       // 日志等级
            pid_t _pid;            // 进程pid
            std::string _filename; // 源文件名称
            int _line;             // 日志所在的行号
            Logger &_logger;       // 负责根据不同的策略进行刷新
            std::string _loginfo;  // 一条完整的日志记录
        };

        // 就是要拷贝,故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

    private:
        std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

11、InetAddr.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"

class InetAddr
{
private:
    void PortNet2Host()
    {
        _port = ::ntohs(_net_addr.sin_port);
    }
    void IpNet2Host()
    {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        (void)ip;
        _ip = ipbuffer;
    }

public:
    InetAddr()
    {
    }
    InetAddr(const struct sockaddr_in &addr) : _net_addr(addr)
    {
        PortNet2Host();
        IpNet2Host();
    }
    bool operator == (const InetAddr &addr)
    {
        return _ip == addr._ip && _port == addr._port; //debug
        //return _ip == addr._ip; //debug
    }
    InetAddr(uint16_t port) : _port(port), _ip("")
    {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = INADDR_ANY;
    }
    struct sockaddr *NetAddr() { return CONV(&_net_addr); }
    socklen_t NetAddrLen() { return sizeof(_net_addr); }
    std::string Ip() { return _ip; }
    uint16_t Port() { return _port; }

    std::string Addr()
    {
        return Ip() + ":" + std::to_string(Port());
    }
    
    ~InetAddr()
    {
    }

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

12、Cond.hpp

#pragma once

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

namespace CondModule
{
    using namespace LockModule;

    class Cond
    {
    public:
        Cond()
        {
            int n = ::pthread_cond_init(&_cond, nullptr);
            (void)n;
        }
        void Wait(Mutex &lock) // 让我们的线程释放曾经持有的锁!
        {
            int n = ::pthread_cond_wait(&_cond, lock.LockPtr());
        }
        void Notify()
        {
            int n = ::pthread_cond_signal(&_cond);
            (void)n;
        }
        void NotifyAll()
        {
            int n = ::pthread_cond_broadcast(&_cond);
            (void)n;
        }
        ~Cond()
        {
            int n = ::pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond;
    };
}

13、Common.hpp

#pragma once

#include <iostream>


#define Die(code)   \
    do              \
    {               \
        exit(code); \
    } while (0)

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

enum 
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

相关文章:

  • C++实用函数:find与find_if
  • 智能汽车图像及视频处理方案,支持视频星轨拍摄能力
  • 【机器学习基础 4】 Pandas库
  • 移植原包ROM通用处理方案
  • 作业12 (2023-05-15 指针概念)
  • Hostapd2.11解析笔记_nl80211接口交互流程_消息收发细节解析
  • vue js给元素动态添加动画样式, 改变背景色
  • 仓库管理4大核心系统(OMS、WMS、WCS、WES)是什么,有何用处?
  • 【系统架构设计师】DNS查询过程
  • Linux基础 -- SoC从uboot到linux kernel的全过程
  • 从感知器准则到最小平方误差准则——与神经网络的发展类比
  • 使用Python可视化图结构:从GraphML文件生成节点关系图(lightrag 生成)
  • FPGA中串行执行方式之流水线(Pipeline)
  • 大数据学习(84)-Hive数仓
  • IIS漏洞攻略
  • C# 属性(Property)‌详解
  • 了解TikTok直播不推流的原因及流量异常的解决方案
  • 基于SpringBoot的电影售票系统
  • spring和maven
  • 内网渗透基础
  • 美国季度GDP时隔三年再现负增长,特朗普政府关税政策对美国经济负面影响或将持续
  • 应急管理部派出工作组赴山西太原小区爆炸现场指导救援处置
  • 莫名的硝烟|“我们最好记住1931年9月18日这个日子”
  • 城市更新·简报│中央财政支持城市更新,倾斜超大特大城市
  • 民营经济促进法出台,自今年5月20日起施行
  • 人民日报:在大有可为的时代大有作为