[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. 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;
};