云计算百科
云计算领域专业知识百科平台

[net 6] udp_chat_server基于udp的简单聊天室(多线程的服务器与业务相分离)

目录

1. 网络聊天室的意义

2. 网络聊天室了解

2.1. 网络聊天室模块分析

2.2. 目标

3. 基本框架

3.1. 文件基本框架

3.2. 设计回调函数解耦

4. Route.hpp 模块(消息转发)

4.1. 头文件包含

4.2. 基本类框架

4.3. Route::Forward() 转发

4.3.1. 函数头设计

4.3.2. 维护用户列表

4.3.3. 客户退出聊天

4.3.4. 消息转发 -> 线程池

4.3.4.1. 引入线程池代码

4.3.4.2. 转发任务

4.4. 转发模块与服务器模块关联

4.5. 测试: 多个客户端与一个服务端

5. 客户端读写并行

5.1. 客户端初始化

5.2. 发消息客户端与收消息客户端解耦合

5.2.1. 收消息

5.2.2. 发消息

6. 参考代码

6.1. 核心代码

6.2. 其他代码


1. 网络聊天室的意义

为什么要写一个网络聊天室呢? 前面都写过字典那个例子了.

意义在于: 网络聊天室的意义在于多进程, 引入使用线程池.

2. 网络聊天室了解

2.1. 网络聊天室模块分析

  • udpserver
    • 功能: 读取数据
    • 读到两个东西: 数据 + clientinfo(ip/port…)
    • 在线用户列表, 维护"谁在线"的含义(ip + port).
  • 路由与转发模块
    • 功能: 转发消息(根据用户列表), 转发给对应的 client.
    • 实现方式: 线程池的方式进行转发.

2.2. 目标

要求与字典服务一样, 要求各个模块解耦合.

业务逻辑 与 IO 逻辑解耦.

3. 基本框架

3.1. 文件基本框架

同样我们把之前写的 echo_server 直接 CV 到我们的 chat_server 即可.

3.2. 设计回调函数解耦

解释一下各个参数的含义:

  • 返回值是 void, 因为是发送消息嘛, 可以不用返回值.
  • 参数 1: int, 这个是套接字
  • 参数 2: 发送的消息
  • 参数 3: 谁发的消息

解耦合: 把服务发消息给到转发模块, 所以下面这部分是不要了.

变成:

4. Route.hpp 模块(消息转发)

4.1. 头文件包含

4.2. 基本类框架

4.3. Route::Forward() 转发

4.3.1. 函数头设计

  • int sockfd: 从哪个套接字发?
  • message: 发送的消息是什么?
  • who: 谁来发?
4.3.2. 维护用户列表

同时, 还需要注意在 InetAddr 当中去重载比较运算符.

如果只保证 ip 的唯一性, 所以一个主机下的一个 ip 只能启动一个客户端, 所以不太好(正常来讲是 ip 唯一的), 我们这里保证端口唯一即可.

4.3.3. 客户退出聊天

我们约定: 假设 message == "QUIT", 我们移除 online_user 中的客户. 并且把这个消息同样做转发处理(告知所有人有个人走了~).

注意: 找到了一定要立刻 break, 这个地方存在迭代器失效问题.

4.3.4. 消息转发 -> 线程池

字节序列问题可以用 sockaddr 直接解决:

下面实际上还有俩活:

  • 引入线程池
  • 业务 与 服务器之间关联起来.
  • 4.3.4.1. 引入线程池代码

    4.3.4.2. 转发任务

    啥意思呢? 你这样直接调用, 是主线程做的, 效率不高, 因此我们考虑把这个 ForwardHelper 任务外包出去, 让线程池的线程来进行处理, 而我们的主线程则有空闲去处理自己的事情.

    包装任务:

    构建任务:

    获取线程池对象并入线程池任务队列:

    4.4. 转发模块与服务器模块关联

    因为线程池中的 func_t 类型已经占用了, 因此我们给 UdpServer.hpp 中的包装器改一下名字.

    服务端对象:

    绑定转发模块, 并且关联到服务器:

    带上线程库进行编译:

    4.5. 测试: 多个客户端与一个服务端

    用 ps -aL可以查询当前的一个线程启动情况.

    下面是一些方便验证的调试消息:

    直接看源码吧, 改的比较多~

    但是发现了一个大问题: 就是多个客户端在跟服务器去交互的时候, 只要你客户端不说话, 你就收不到消息, 虽然说服务器把消息转发了, 这就相当逆天!

    因为客户端阻塞在发消息当中. 你想接受消息就必须发消息, 因为是阻塞状态嘛, 这就很坑爹. 所以说, 我们可以给每个客户端搞两个线程, 一个读一个发消息, 两个并行跑这样的话可能会比较好.

    5. 客户端读写并行

    5.1. 客户端初始化

    5.2. 发消息客户端与收消息客户端解耦合

    客户端加入线程模块:

    创建读写线程并且启动:

    主线程负责等待两个线程即可:

    我们收消息的线程需要用到套接字, 因此给他传过去即可, 因为我们的 thread 自己封装的线程库只要求一个参数, 所以我们 bind 给他过去即可.

    到了发消息呢? 发消息的线程需要套接字, 还需要知道服务器的 ip + port. 这个地方老师又把命令行参数放到 main 里了.

    5.2.1. 收消息

    上面 code 用的是 cerr, 是方便用来重定向打印到两个不同终端看效果的.

    5.2.2. 发消息

    定位一下不同的终端号:

    将这个程序的标准错误全部重定向到 0 号终端.

    6. 参考代码

    6.1. 核心代码

    ip + port -> 标识客户端的唯一性.

    #include "UdpServer.hpp"
    #include "Route.hpp"

    #include <memory>

    // ./udp_server local-port
    // ./udp_server 8888
    int main(int argc, char *argv[])
    {
    if(argc != 2)
    {
    std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
    exit(0);
    }
    uint16_t port = std::stoi(argv[1]);

    EnableScreen();

    Route messageRoute; // 实例化转发模块

    service_t message_route = std::bind(&Route::Forward,\\
    &messageRoute, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    // 把转发模块中的转发函数给到服务器用来绑定到一起.

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(message_route, port); //C++14的标准
    usvr->InitServer();
    usvr->Start();
    return 0;
    }
    #pragma once

    #include <iostream>
    #include <unistd.h>
    #include <string>
    #include <cstring>
    #include <functional>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    #include "nocopy.hpp"
    #include "Log.hpp"
    #include "InetAddr.hpp"
    /*
    * 服务器用来读取数据
    * 读取的数据有两个东西
    * – 用户发送的数据/信息 -> 交给 [路由转发模块] 进行处理.
    * – 用户本身的信息 -> ip + port 构成一个 在线用户列表.
    * [路由转发模块] 根据 在线用户列表 来转发给 所有人 消息.
    * 线程池帮助路由转发模块进行转发.

    目标: 服务器, 转发模块 以及 线程池 全部解耦合!
    */
    using namespace log_ns;

    static const int gsockfd = -1;
    static const uint16_t glocalport = 8888;

    enum
    {
    SOCKET_ERROR = 1,
    BIND_ERROR
    };

    using service_t = std::function<void(int, const std::string &message, InetAddr &who)>;
    // 转发模块的函数指针. -> 帮助我们服务器来转发消息的.
    // 参数1: 从哪个套接字发消息?
    // 参数2: message -> 转发的消息内容
    // 参数3: 谁发的这个消息 -> 因为需要用到用户的ip + port来标识用户的唯一性.

    // UdpServer user("192.1.1.1", 8899);
    // 一般服务器主要是用来进行网络数据读取和写入的。IO的
    // 服务器IO逻辑 和 业务逻辑 解耦
    class UdpServer : public nocopy
    {
    public:
    UdpServer(service_t func, uint16_t localport = glocalport)
    : _func(func),
    _sockfd(gsockfd),
    _localport(localport),
    _isrunning(false)
    {
    }
    void InitServer()
    {
    // 1. 创建socket文件
    _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (_sockfd < 0)
    {
    LOG(FATAL, "socket error\\n");
    exit(SOCKET_ERROR);
    }
    LOG(DEBUG, "socket create success, _sockfd: %d\\n", _sockfd); // 3

    // 2. bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(_localport);
    // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP — 暂时
    local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定

    int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
    if (n < 0)
    {
    LOG(FATAL, "bind error\\n");
    exit(BIND_ERROR);
    }
    LOG(DEBUG, "socket bind success\\n");
    }
    void Start()
    {
    _isrunning = true;
    char message[1024];
    while (_isrunning)
    {
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    ssize_t n = recvfrom(_sockfd, message, sizeof(message) – 1, 0, (struct sockaddr *)&peer, &len);
    if (n > 0)
    {
    InetAddr addr(peer);
    message[n] = 0; // 收到消息的缓冲区.
    LOG(DEBUG, "[%s]# %s\\n", addr.AddrStr().c_str(), message); // 提示一下是否接受消息成功.

    _func(_sockfd, message, addr); // 把转发任务给到转发模块进行处理.
    LOG(DEBUG, "return udpserver\\n"); // 提示回调完成!
    }
    else
    {
    std::cout << "recvfrom , error" << std::endl;
    }
    }
    _isrunning = false;
    }
    ~UdpServer()
    {
    if (_sockfd > gsockfd)
    ::close(_sockfd);
    }

    private:
    int _sockfd; // 读写都用同一个sockfd, 反应说明:UDP是 全双工 通信的!
    uint16_t _localport;
    // std::string _localip; // TODO:后面专门要处理一下这个IP
    bool _isrunning;

    service_t _func; // 该函数用来服务器回调转发模块, 是一个回调函数指针.
    };
    #include <iostream>
    #include <string>
    #include <cstring>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include "Thread.hpp"

    /*
    – 客户端要一边读一边写 -> 客户端要多线程化!
    – 线程1: 读数据 从服务端那边随时接受消息, 并立刻显示到终端上.
    – 线程2: 发消息, 从键盘上获取用户的消息, 显示到终端上, 并立刻发送给服务器.
    */
    // ./udpclient 127.0.0.1 8888 2>/dev/pts/0 -> 把./udpclient进程的标准错误信息都重定向到0号虚拟终端下, 方便观察现象.
    using namespace ThreadMoudle;

    int InitClient()
    {
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
    std::cerr << "create socket error" << std::endl;
    exit(1);
    }
    return sockfd;
    }

    // 读线程: 需要知道从哪个socket读,
    void RecvMessage(int sockfd, const std::string &name)
    {
    // 接受消息是不断去循环接受的.
    while (true)
    {
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    char buffer[1024];
    int n = recvfrom(sockfd, buffer, sizeof(buffer) – 1, 0, (struct sockaddr *)&peer, &len);
    if (n > 0)
    {
    buffer[n] = 0;
    std::cerr << buffer << std::endl; // 读取成功, 咱们打印出来.
    }
    else
    {
    std::cerr << "recvfrom error" << std::endl; // 读取失败, 咱们提示一下.
    break;
    }
    }
    }

    void SendMessage(int sockfd, std::string serverip, uint16_t serverport, const std::string &name)
    {
    // 填写目标结构体
    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());

    // 准备要发送的内容的前缀.
    std::string cli_profix = name + "# "; // sender-thread# 你好
    while (true)
    {
    std::string line; // 缓冲区
    std::cout << cli_profix; // 打印发送前缀
    std::getline(std::cin, line); // 获取用户输入的消息
    int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server)); // 把消息发给server.
    if (n <= 0)
    break;
    }
    }

    int main(int argc, char *argv[])
    {
    // 检查参数
    if (argc != 3)
    {
    std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
    exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    int sockfd = InitClient();

    // 收消息的线程 -> 并把收消息的函数给到该线程
    Thread recver("recver-thread", std::bind(&RecvMessage, sockfd, std::placeholders::_1));
    // string 用来让Thread标识线程名, func(string), 是为了方便在func里打印线程名.
    // 发消息的线程 -> 并把发消息的函数给到该线程
    Thread sender("sender-thread", std::bind(&SendMessage, sockfd, serverip, serverport, std::placeholders::_1));

    // 两个线程启动起来
    recver.Start();
    sender.Start();

    // 主线程等待两个线程
    recver.Join();
    sender.Join();

    ::close(sockfd); // 关闭文件描述符
    return 0;
    }
    #pragma once
    #include <iostream>
    #include <string>
    #include <vector>
    #include <functional>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <pthread.h>
    #include "InetAddr.hpp"
    #include "ThreadPool.hpp"
    #include "LockGuard.hpp"

    // class user
    // {};

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

    class Route
    {
    public:
    Route()
    {
    pthread_mutex_init(&_mutex, nullptr);
    }
    // 检查用户是否在在线用户列表当中 + 对应的行为.
    // 不在 -> 添加用户.
    // 在 -> 什么都不做.
    void CheckOnlineUser(InetAddr &who)
    {
    LockGuard lockguard(&_mutex);
    for (auto &user : _online_user)
    {
    if (user == who) // 如果存在, 就什么都不做. -> 重载InetAddr的比较bool operator==().
    {
    LOG(DEBUG, "%s is exists\\n", who.AddrStr().c_str()); // 存在的用户消息提示.
    return; // 直接退出
    }
    }
    LOG(DEBUG, "%s is not exists, add it\\n", who.AddrStr().c_str()); // 不存在的用户也提示一下.
    _online_user.push_back(who); // 不在, 我们就添加进去.
    }
    // for test

    // 在用户列表当中移除用户.
    void Offline(InetAddr &who)
    {
    LockGuard lockguard(&_mutex); // 加锁.
    auto iter = _online_user.begin();
    for (; iter != _online_user.end(); iter++)
    {
    if (*iter == who)
    {
    LOG(DEBUG, "%s is offline\\n", who.AddrStr().c_str()); // 日志: 提示某用户离开.
    _online_user.erase(iter);
    break; // 迭代器失效, 直接break;
    }
    }
    }

    void ForwardHelper(int sockfd, const std::string message, InetAddr who)
    {
    // 从哪转发, 哪个套接字? -> sockfd
    // 转发啥消息? -> message
    // 谁发的? -> who
    LockGuard lockguard(&_mutex); // 加锁.
    std::string send_message = "[" + who.AddrStr() + "]# " + message; // 准备要转发的消息
    for (auto &user : _online_user)
    {
    struct sockaddr_in peer = user.Addr(); // Addr(): 返回的是网络序列的套接字信息.
    LOG(DEBUG, "Forward message to %s, message is %s\\n", user.AddrStr().c_str(), send_message.c_str()); // debug: 即将转发的消息也提示一下.
    ::sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&peer, sizeof(peer));
    }
    }

    // 消息转发
    void Forward(int sockfd, const std::string &message, InetAddr &who)
    {
    // 1. 该用户是否在 在线用户列表中呢?如果在,什么都不做,如果不在,自动添加到_online_user
    CheckOnlineUser(who);

    // 1.1 用户可选择退出: message == "QUIT" "Q"
    if (message == "QUIT" || message == "Q")
    {
    Offline(who);
    }

    // 2. who 一定在_online_user列表里面
    // ForwardHelper(sockfd, message); // 这样是路由模块/服务器进行消息转发, 我们下面这种写法就算把这个转发任务给到线程.
    task_t t = std::bind(&Route::ForwardHelper, this, sockfd, message, who); // 包装一下转发函数. 因为线程池中的线程只接受void()类型的行为.
    ThreadPool<task_t>::GetInstance()->Equeue(t); // 启动线程池, 获取一个线程池单例对象 -> 派发任务给他的子线程.
    }
    ~Route()
    {
    pthread_mutex_destroy(&_mutex);
    }

    private:
    std::vector<InetAddr> _online_user; // 维护在线用户列表.
    pthread_mutex_t _mutex; // 用来保护公共资源.
    };
    #pragma once

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

    class InetAddr
    {
    private:
    void ToHost(const struct sockaddr_in &addr)
    {
    _port = ntohs(addr.sin_port);
    // _ip = inet_ntoa(addr.sin_addr);
    char ip_buf[32];
    // inet_p to n
    // p: process
    // n: net
    // inet_pton(int af, const char *src, void *dst);
    // inet_pton(AF_INET, ip.c_str(), &addr.sin_addr.s_addr);
    ::inet_ntop(AF_INET, &addr.sin_addr, ip_buf, sizeof(ip_buf));
    _ip = ip_buf;
    }

    public:
    InetAddr(const struct sockaddr_in &addr):_addr(addr)
    {
    ToHost(addr);
    }

    // ip + port 唯一性:
    // 一般来说ip唯一就算是唯一的.
    // 这里为了方便测试, 把port也搞成区分唯一性的.
    bool operator == (const InetAddr &addr)
    {
    return (this->_ip == addr._ip && this->_port == addr._port);
    }
    std::string Ip()
    {
    return _ip;
    }
    uint16_t Port()
    {
    return _port;
    }
    struct sockaddr_in Addr()
    {
    return _addr;
    }
    // 用来方便调试, 返回的是字符串, 方便打印.
    std::string AddrStr()
    {
    return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr()
    {
    }

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

    6.2. 其他代码

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

    namespace ThreadMoudle
    {
    // 线程要执行的方法,后面我们随时调整
    // typedef void (*func_t)(ThreadData *td); // 函数指针类型

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

    class Thread
    {
    public:
    void Excute()
    {
    _isrunning = true;
    _func(_name);
    _isrunning = false;
    }
    public:
    Thread(const std::string &name, func_t func):_name(name), _func(func)
    {
    }
    static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
    {
    Thread *self = static_cast<Thread*>(args); // 获得了当前对象
    self->Excute();
    return nullptr;
    }
    bool Start()
    {
    int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
    if(n != 0) return false;
    return true;
    }
    std::string Status()
    {
    if(_isrunning) return "running";
    else return "sleep";
    }
    void Stop()
    {
    if(_isrunning)
    {
    ::pthread_cancel(_tid);
    _isrunning = false;
    }
    }
    void Join()
    {
    ::pthread_join(_tid, nullptr);
    }
    std::string Name()
    {
    return _name;
    }
    ~Thread()
    {
    }

    private:
    std::string _name;
    pthread_t _tid;
    bool _isrunning;
    func_t _func; // 线程要执行的回调函数
    };
    } // namespace ThreadModle

    #pragma once

    #include <iostream>
    #include <unistd.h>
    #include <string>
    #include <vector>
    #include <queue>
    #include <functional>
    #include "Thread.hpp"
    #include "Log.hpp"
    #include "LockGuard.hpp"

    using namespace ThreadMoudle;
    using namespace log_ns;

    static const int gdefaultnum = 5;

    void test()
    {
    while (true)
    {
    std::cout << "hello world" << std::endl;
    sleep(1);
    }
    }

    template <typename T>
    class ThreadPool
    {
    private:
    void LockQueue()
    {
    pthread_mutex_lock(&_mutex);
    }
    void UnlockQueue()
    {
    pthread_mutex_unlock(&_mutex);
    }
    void Wakeup()
    {
    pthread_cond_signal(&_cond);
    }
    void WakeupAll()
    {
    pthread_cond_broadcast(&_cond);
    }
    void Sleep()
    {
    pthread_cond_wait(&_cond, &_mutex);
    }
    bool IsEmpty()
    {
    return _task_queue.empty();
    }
    void HandlerTask(const std::string &name) // this
    {
    while (true)
    {
    // 取任务
    LockQueue();
    while (IsEmpty() && _isrunning)
    {
    _sleep_thread_num++;
    LOG(INFO, "%s thread sleep begin!\\n", name.c_str());
    Sleep();
    LOG(INFO, "%s thread wakeup!\\n", name.c_str());
    _sleep_thread_num–;
    }
    // 判定一种情况
    if (IsEmpty() && !_isrunning)
    {
    UnlockQueue();
    LOG(INFO, "%s thread quit\\n", name.c_str());
    break;
    }

    // 有任务
    T t = _task_queue.front();
    _task_queue.pop();
    UnlockQueue();

    // 处理任务
    t(); // 处理任务,此处不用/不能在临界区中处理
    // std::cout << name << ": " << t.result() << std::endl;
    // LOG(DEBUG, "hander task done, task is : %s\\n", t.result().c_str());
    }
    }
    void Init()
    {
    func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
    for (int i = 0; i < _thread_num; i++)
    {
    std::string threadname = "thread-" + std::to_string(i + 1);
    _threads.emplace_back(threadname, func);
    LOG(DEBUG, "construct thread %s done, init success\\n", threadname.c_str());
    }
    }
    void Start()
    {
    _isrunning = true;
    for (auto &thread : _threads)
    {
    LOG(DEBUG, "start thread %s done.\\n", thread.Name().c_str());
    thread.Start();
    }
    }
    ThreadPool(int thread_num = gdefaultnum)
    : _thread_num(thread_num), _isrunning(false), _sleep_thread_num(0)
    {
    pthread_mutex_init(&_mutex, nullptr);
    pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T> &) = delete;

    public:
    void Stop()
    {
    LockQueue();
    _isrunning = false;
    WakeupAll();
    UnlockQueue();
    LOG(INFO, "Thread Pool Stop Success!\\n");
    }

    // 如果是多线程获取单例呢?
    static ThreadPool<T> *GetInstance()
    {
    if (_tp == nullptr)
    {
    LockGuard lockguard(&_sig_mutex);
    if (_tp == nullptr)
    {
    LOG(INFO, "create threadpool\\n");
    // thread-1 thread-2 thread-3….
    _tp = new ThreadPool<T>();
    _tp->Init();
    _tp->Start();
    }
    else
    {
    LOG(INFO, "get threadpool\\n");
    }
    }
    return _tp;
    }

    void Equeue(const T &in)
    {
    LockQueue();
    if (_isrunning)
    {
    _task_queue.push(in);
    if (_sleep_thread_num > 0)
    Wakeup();
    }
    UnlockQueue();
    }
    ~ThreadPool()
    {
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
    }

    private:
    int _thread_num;
    std::vector<Thread> _threads;
    std::queue<T> _task_queue;
    bool _isrunning;

    int _sleep_thread_num;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    // 单例模式
    // volatile static ThreadPool<T> *_tp;
    static ThreadPool<T> *_tp;
    static pthread_mutex_t _sig_mutex;
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
    template <typename T>
    pthread_mutex_t ThreadPool<T>::_sig_mutex = PTHREAD_MUTEX_INITIALIZER;

    #pragma once

    #include <pthread.h>

    class LockGuard
    {
    public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
    pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
    pthread_mutex_unlock(_mutex);
    }
    private:
    pthread_mutex_t *_mutex;
    };
    #pragma once

    #include <iostream>
    #include <sys/types.h>
    #include <unistd.h>
    #include <ctime>
    #include <cstdarg>
    #include <fstream>
    #include <cstring>
    #include <pthread.h>
    #include "LockGuard.hpp"

    namespace log_ns
    {

    enum
    {
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
    };

    std::string LevelToString(int level)
    {
    switch (level)
    {
    case DEBUG:
    return "DEBUG";
    case INFO:
    return "INFO";
    case WARNING:
    return "WARNING";
    case ERROR:
    return "ERROR";
    case FATAL:
    return "FATAL";
    default:
    return "UNKNOWN";
    }
    }

    std::string GetCurrTime()
    {
    time_t now = time(nullptr);
    struct tm *curr_time = localtime(&now);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
    curr_time->tm_year + 1900,
    curr_time->tm_mon + 1,
    curr_time->tm_mday,
    curr_time->tm_hour,
    curr_time->tm_min,
    curr_time->tm_sec);
    return buffer;
    }

    class logmessage
    {
    public:
    std::string _level;
    pid_t _id;
    std::string _filename;
    int _filenumber;
    std::string _curr_time;
    std::string _message_info;
    };

    #define SCREEN_TYPE 1
    #define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    // log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );
    class Log
    {
    public:
    Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
    {
    }
    void Enable(int type)
    {
    _type = type;
    }
    void FlushLogToScreen(const logmessage &lg)
    {
    printf("[%s][%d][%s][%d][%s] %s",
    lg._level.c_str(),
    lg._id,
    lg._filename.c_str(),
    lg._filenumber,
    lg._curr_time.c_str(),
    lg._message_info.c_str());
    }
    void FlushLogToFile(const logmessage &lg)
    {
    std::ofstream out(_logfile, std::ios::app);
    if (!out.is_open())
    return;
    char logtxt[2048];
    snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
    lg._level.c_str(),
    lg._id,
    lg._filename.c_str(),
    lg._filenumber,
    lg._curr_time.c_str(),
    lg._message_info.c_str());
    out.write(logtxt, strlen(logtxt));
    out.close();
    }
    void FlushLog(const logmessage &lg)
    {
    // 加过滤逻辑 — TODO

    LockGuard lockguard(&glock);
    switch (_type)
    {
    case SCREEN_TYPE:
    FlushLogToScreen(lg);
    break;
    case FILE_TYPE:
    FlushLogToFile(lg);
    break;
    }
    }
    void logMessage(std::string filename, int filenumber, int level, const char *format, …)
    {
    logmessage lg;

    lg._level = LevelToString(level);
    lg._id = getpid();
    lg._filename = filename;
    lg._filenumber = filenumber;
    lg._curr_time = GetCurrTime();

    va_list ap;
    va_start(ap, format);
    char log_info[1024];
    vsnprintf(log_info, sizeof(log_info), format, ap);
    va_end(ap);
    lg._message_info = log_info;

    // 打印出来日志
    FlushLog(lg);
    }
    ~Log()
    {
    }

    private:
    int _type;
    std::string _logfile;
    };

    Log lg;

    #define LOG(Level, Format, …) \\
    do \\
    { \\
    lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \\
    } while (0)
    #define EnableScreen() \\
    do \\
    { \\
    lg.Enable(SCREEN_TYPE); \\
    } while (0)
    #define EnableFILE() \\
    do \\
    { \\
    lg.Enable(FILE_TYPE); \\
    } while (0)
    };
    .PHONY:all
    all:udpserver udpclient

    udpserver:UdpServerMain.cc
    g++ -o $@ $^ -std=c++14 -lpthread // 线程库
    udpclient:UdpClientMain.cc
    g++ -o $@ $^ -std=c++14 -lpthread // 线程库

    .PHONY:clean
    clean:
    rm -rf udpserver udpclient
    #pragma once

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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » [net 6] udp_chat_server基于udp的简单聊天室(多线程的服务器与业务相分离)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!