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

[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 直接解决:

下面实际上还有俩活:

  1. 引入线程池
  2. 业务 与 服务器之间关联起来.
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. bindstruct 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:后面专门要处理一下这个IPbool _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// 谁发的? -> whoLockGuard 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_userCheckOnlineUser(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 2const 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){// 加过滤逻辑 --- TODOLockGuard 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 udpclientudpserver:UdpServerMain.ccg++ -o $@ $^ -std=c++14 -lpthread // 线程库
udpclient:UdpClientMain.ccg++ -o $@ $^ -std=c++14 -lpthread // 线程库.PHONY:clean
clean:rm -rf udpserver udpclient
#pragma onceclass nocopy
{
public:nocopy(){}~nocopy(){}nocopy(const nocopy&) = delete;const nocopy& operator=(const nocopy&) = delete;
};

相关文章:

  • C++11_2
  • 信息学奥赛一本通 1622:Goldbach’s Conjecture | 洛谷 UVA543 Goldbach‘s Conjecture
  • 【HDFS入门】HDFS与Hadoop生态的深度集成:与YARN、MapReduce和Hive的协同工作原理
  • 深度监听 ref 和 reactive 的区别详解
  • Spring Boot 实现 Excel 导出功能(支持前端下载 + 文件流)
  • ⭐ Unity 使用Odin Inspector增强编辑器的功能:UIManager脚本实例
  • React 对state进行保留和重置
  • 【gpt生成-其一】以go语言为例,详细描述一下 ​:语法规范​​BNF/EBNF形式化描述
  • 基于深度学习并利用时间信息在X射线血管造影中进行冠状动脉血管分割|文献速递-深度学习医疗AI最新文献
  • 【gpt生成-总览】怎样才算开发了一门编程语言,需要通过什么测试
  • 【OSCP-vulnhub】GoldenEye
  • 【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁
  • v-model进阶+ref+nextTick
  • 爱普生FA2016AS晶振在智能家居中的应用
  • vue3项目启动bug
  • GitHub 从入门到精通完全指南(2025版)
  • 【FPGA】【DE2-115】DDS信号发生器设计
  • 游戏引擎学习第229天
  • RAG(检索增强生成)、ReAct(推理与行动) 和 多模态AI 的详细解析,包括三者的定义、工作原理、应用场景及协同关系
  • docker能用来干什么的
  • 北斗系统全面进入11个国际组织的标准体系
  • 2025年上海科技节开幕,人形机器人首次登上科学红毯
  • 英国6月初将公布对华关系的审计报告,外交部:望英方树立正确政策导向
  • 云南德宏州盈江县发生4.5级地震,震源深度10千米
  • 上海锦江乐园摩天轮正在拆除中,预计5月底6月初拆完
  • 中国科学院院士、我国航天液体火箭技术专家朱森元逝世