目录
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;
};
评论前必须登录!
注册