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

【网络套接字编程】基于UDP协议的公共聊天室项目

目录

  • 一、项目构建思路
  • 二、实现
    • 1.服务器端(UdpServer.hpp)
    • 2.通信模块(MessageRoute.hpp)
    • 3.客户端(UdpClient.cc)
  • 三、运行测试
  • 四、源代码
    • UdpServer.hpp
    • MessageRoute.hpp
    • UdpClient.cc
    • InetAddr.hpp
    • Thread.hpp
    • ThreadPool.hpp
    • Main.cc
    • LockGuard.hpp
    • Log.hpp

一、项目构建思路

这次和上次的简单聊天项目不同,我们要建造一个公共的聊天室,一个用户发的信息所有用户都能够看见

在客户端与服务端方面,服务端收到大量客户端的信息,然后服务端通过一个通信模块,将我们的消息包装为函数对象,将这个函数对象作为任务,通过线程池转发出去,从而达到高效的目的

在这里插入图片描述

二、实现

1.服务器端(UdpServer.hpp)

#include"UdpServer.hpp"
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<cstring>
#include<string.h>
#include"Log.hpp"
#include"LockGuard.hpp"
#include<arpa/inet.h>
#include<cstdlib>
#include<netinet/in.h>void Usage(std::string proc)
{std::cout<<"Usage: \n\t"<<proc<<" serverip serverport"<<std::endl;
}int main(int argc,char* argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}std::string serverip=argv[1];uint16_t serverport=atoi(argv[2]);//1.创建sockfdint sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){std::cerr<<"socket error"<<std::endl;exit(1);}//2.填充服务器的网络信息结构体struct sockaddr_in server;bzero(&server,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());//3.发送数据std::string message;while(true){std::cout<<"Please Enter:#";std::getline(std::cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));struct sockaddr_in peer;socklen_t len=sizeof(peer);char buffer[1024];ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;std::cout<<"Server Echo# "<<buffer<<std::endl;}}return 0;
}

前面都是一些网络结构填充什么的,关键代码是将收到的消息通过通信模块转发出去

 using hander_message =std::function<void(int socketd,std::string message,const InetAddr user)>;hander_message(_sockfd,message,addr);private:func_t hander_message;

这里通过包装器来对通信模块中的消息转发函数进行回调,就是消息收到了,把转发消息的任务通过函数丢给通信模块去做

2.通信模块(MessageRoute.hpp)

#pragma once#include<iostream>
#include<string>
#include<functional>
#include<vector>
#include"LockGuard.hpp"
#include<sys/socket.h>
#include<sys/types.h>
#include"InetAddr.hpp"
#include"ThreadPool.hpp"using task_t=std::function<void()>;class MessageRoute
{
public:MessageRoute(){pthread_mutex_init(&_mutex,nullptr);}void AddUser(const InetAddr& user){LockGuard lock(&_mutex);if(std::find(_online_user.begin(),_online_user.end(),user)!=_online_user.end()){return;}_online_user.push_back(user);}void DelUser(const InetAddr& user){LockGuard lock(&_mutex);for(auto it=_online_user.begin();it!=_online_user.end();++it){if(*it==user){_online_user.erase(it);break;}}}void RouteHelper(int socket,std::string& message,InetAddr user){LockGuard lock(&_mutex);//2.进行消息转发for(auto online_user:_online_user){std::string send_message="\n["+user.Ip()+":"+std::to_string(user.Port())+"]# "+message+"\n";struct sockaddr_in clientaddr=online_user.Addr();::sendto(socket,send_message.c_str(),send_message.size(),0,(struct sockaddr*)&clientaddr,sizeof(clientaddr));}}void Route(int socket,std::string& message,InetAddr user){//1.用户首次发消息,将自己的信息添加到在线用户列表中AddUser(user);//如果用户要退出,将自己的信息从在线用户列表中删除if(message=="quit"||message=="Q"){DelUser(user);}//2.构建任务对象,入队列,让线程池进行转发task_t t=std::bind(&MessageRoute::RouteHelper,this,socket,message,user);ThreadPool<task_t>::GetInstance()->Enqueue(t);}~MessageRoute(){pthread_mutex_destroy(&_mutex);}privare:std::vector<InetAddr> _online_user;pthread_mutex_t _mutex;
};

这是一个消息路由类,用于管理在线用户和消息转发

  1. 主要功能:
  • 管理在线用户列表(增删查)
  • 实现消息广播转发
  • 线程安全的用户管理

关键地方时消息转发模块

void RouteHelper(int socket,std::string& message,InetAddr user){LockGuard lock(&_mutex);//2.进行消息转发for(auto online_user:_online_user){std::string send_message="\n["+user.Ip()+":"+std::to_string(user.Port())+"]# "+message+"\n";struct sockaddr_in clientaddr=online_user.Addr();::sendto(socket,send_message.c_str(),send_message.size(),0,(struct sockaddr*)&clientaddr,sizeof(clientaddr));}}void Route(int socket,std::string& message,InetAddr user){//1.用户首次发消息,将自己的信息添加到在线用户列表中AddUser(user);//如果用户要退出,将自己的信息从在线用户列表中删除if(message=="quit"||message=="Q"){DelUser(user);}//2.构建任务对象,入队列,让线程池进行转发task_t t=std::bind(&MessageRoute::RouteHelper,this,socket,message,user);ThreadPool<task_t>::GetInstance()->Enqueue(t);}

通过bind函数创建可调用的任务对象,将这个任务对象放入线程池,由线程池进行转发

通过遍历所有的用户,使得无论是谁发信息,这个信息都会转发给所有人,从而达成一个公共聊天室的目的

3.客户端(UdpClient.cc)

#include"UdpServer.hpp"
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<cstring>
#include<string.h>
#include"Log.hpp"
#include"LockGuard.hpp"
#include<arpa/inet.h>
#include<cstdlib>
#include<netinet/in.h>
#include"ThreadPool.hpp"
#include"Thread.hpp"
#include<memory>using namespace ThreadModule;
void Usage(std::string proc)
{std::cout<<"Usage: \n\t"<<proc<<" serverip serverport"<<std::endl;
}int InitClient(std::string serverip,uint16_t serverport,struct sockaddr_in* server)
{//1.创建socket套接字int sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){LOG(FATAL,"socket error,%s,%d\n",strerror(errno),errno);exit(1);}//2.填充服务器的网络信息结构体memset(server,0,sizeof(struct sockaddr_in));server->sin_family=AF_INET;server->sin_port=htons(serverport);server->sin_addr.s_addr=inet_addr(serverip.c_str());return sockfd;
}void recvmessage(int sockfd,std::string name)
{while(true){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;fprintf(stderr, "[%s]%s\n", name.c_str(), buffer); // stderr}}
}void sendmessage(int sockfd,struct sockaddr_in& server,std::string name)
{std::string message;while(true){printf("%s | Enter# ", name.c_str());std::getline(std::cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));}
}int main(int argc,char* argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}std::string serverip=argv[1];uint16_t serverport=atoi(argv[2]);struct sockaddr_in serveraddr;//1.创建sockfd,填充服务器的网络信息结构体int sockfd=InitClient(serverip,serverport,&serveraddr);if(sockfd<0){LOG(FATAL,"InitClient error\n");exit(1);} //2.创建线程,分别进行收消息和发消息func_t r=std::bind(recvmessage,sockfd,std::placeholders::_1);func_t s=std::bind(sendmessage,sockfd,serveraddr,std::placeholders::_1);Thread Recver(r,"recver");Thread Sender(s,"sender");Recver.Start();Sender.Start();Recver.Join();Sender.Join();return 0;
}

1.一开始还是填充网络字段,不过这里将初始化封装了
2.核心是转发部分,我们将收信息和发信息通过bind函数利用线程池使他们成为两个独立的线程,每个用户都能接受任意一个用户发送到服务端的信息,达到了一个公共聊天室的目的

主体代码已经介绍完毕,剩下的代码是一些辅助代码,但同时就不一一介绍了,会在下方以源码贴出

三、运行测试

在这里插入图片描述
每个用户发的信息都能被所有人看到,目的达成

四、源代码

UdpServer.hpp

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROE
};const static int defaultfd=-1;using hander_message_t =std::function<void(int socketd,const std::string message,const InetAddr who)>;class UdpServer
{
public:UdpServer(uint16_t port,hander_message_t hander_message):_sockfd(defaultfd),_port(port),_isrunning(false),_hander_message(hander_message){}void InitServer(){//1.创建UDP socket套接字 ---必须要做的_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){LOG(FATAL,"socket errot,%s,%d\n",strerror(errno),errno);exit(SOCKET_ERROR);}LOG(INFO,"socket create success,sockfd: %d\n",_sockfd);//2.0 填充sockaddr_in结构struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量,在用户栈上开辟bzero(&local,sizeof(local));//清零结构体的所有字节清零,确保没有残留数据。local.sin_family=AF_INET;local.sin_port=htons(_port);//将端口号从主机字节序转换为网络字节序,并存储在 sin_port 成员中字符串风格的点分十进制IP地址转成4字节IPlocal.sin_addr.s_addr=INADDR_ANY;//将 IP 地址设置为 INADDR_ANY ,表示服务器将监听所有可用的网络接口。//bind(绑定) sockfd和网络信息(IP(?)+ Port)int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL,"bind error,%s,%d\n",strerror(errno),errno);exit(BIND_ERROR);}LOG(INFO,"socket bind success\n");}void Start(){//一直运行的_isrunning=true;while(true){char message[1024];struct sockaddr_in peer;socklen_t len =sizeof(peer);//1.让server先收集数据ssize_t n=recvfrom(_sockfd,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);if(n>0){message[n]=0;InetAddr addr(peer);LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),message);_hander_message(_sockfd,message,addr);}}_isrunning=false;}~UdpServer(){}
private:int _sockfd;//std::string _ip; //IP不是必须的uint16_t _port;//端口号bool _isrunning;hander_message_t _hander_message;
};

MessageRoute.hpp

#pragma once#include<iostream>
#include<string>
#include<functional>
#include<vector>
#include"LockGuard.hpp"
#include<sys/socket.h>
#include<sys/types.h>
#include"InetAddr.hpp"
#include"ThreadPool.hpp"using task_t=std::function<void()>;class MessageRoute
{private:bool IsExists(const InetAddr &addr){for (auto a : _online_user){if (a == addr)return true;}return false;}
public:MessageRoute(){pthread_mutex_init(&_mutex,nullptr);}void AddUser(const InetAddr& addr){LockGuard lock(&_mutex);if (IsExists(addr))return;_online_user.push_back(addr);}void DelUser(const InetAddr& user){LockGuard lock(&_mutex);for(auto it=_online_user.begin();it!=_online_user.end();++it){if(*it==user){_online_user.erase(it);break;}}}void RouteHelper(int socket,std::string& message,InetAddr user){LockGuard lock(&_mutex);//2.进行消息转发for(auto online_user:_online_user){std::string send_message="\n["+user.Ip()+":"+std::to_string(user.Port())+"]# "+message+"\n";struct sockaddr_in clientaddr=online_user.Addr();::sendto(socket,send_message.c_str(),send_message.size(),0,(struct sockaddr*)&clientaddr,sizeof(clientaddr));}}void Route(int socket, std::string message,InetAddr user){//1.用户首次发消息,将自己的信息添加到在线用户列表中AddUser(user);//如果用户要退出,将自己的信息从在线用户列表中删除if(message=="quit"||message=="Q"){DelUser(user);}//2.构建任务对象,入队列,让线程池进行转发task_t t=std::bind(&MessageRoute::RouteHelper,this,socket,message,user);ThreadPool<task_t>::GetInstance()->Enqueue(t);}~MessageRoute(){pthread_mutex_destroy(&_mutex);}private:std::vector<InetAddr> _online_user;pthread_mutex_t _mutex;
};

UdpClient.cc

#include"UdpServer.hpp"
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<cstring>
#include<string.h>
#include"Log.hpp"
#include"LockGuard.hpp"
#include<arpa/inet.h>
#include<cstdlib>
#include<netinet/in.h>
#include"ThreadPool.hpp"
#include"Thread.hpp"
#include<memory>using namespace ThreadModule;
void Usage(std::string proc)
{std::cout<<"Usage: \n\t"<<proc<<" serverip serverport"<<std::endl;
}int InitClient(std::string serverip,uint16_t serverport,struct sockaddr_in* server)
{//1.创建socket套接字int sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){LOG(FATAL,"socket error,%s,%d\n",strerror(errno),errno);exit(1);}//2.填充服务器的网络信息结构体memset(server,0,sizeof(struct sockaddr_in));server->sin_family=AF_INET;server->sin_port=htons(serverport);server->sin_addr.s_addr=inet_addr(serverip.c_str());return sockfd;
}void recvmessage(int sockfd,std::string name)
{while(true){char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){buffer[n]=0;fprintf(stderr, "[%s]%s\n", name.c_str(), buffer); // stderr}}
}void sendmessage(int sockfd,struct sockaddr_in& server,std::string name)
{std::string message;while(true){printf("%s | Enter# ", name.c_str());std::getline(std::cin,message);sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));}
}int main(int argc,char* argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}std::string serverip=argv[1];uint16_t serverport=atoi(argv[2]);struct sockaddr_in serveraddr;//1.创建sockfd,填充服务器的网络信息结构体int sockfd=InitClient(serverip,serverport,&serveraddr);if(sockfd<0){LOG(FATAL,"InitClient error\n");exit(1);} //2.创建线程,分别进行收消息和发消息func_t r=std::bind(recvmessage,sockfd,std::placeholders::_1);func_t s=std::bind(sendmessage,sockfd,serveraddr,std::placeholders::_1);Thread Recver(r,"recver");Thread Sender(s,"sender");Recver.Start();Sender.Start();Recver.Join();Sender.Join();return 0;
}

InetAddr.hpp

#pragma once
#include <string>
#include<iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t* port){*port=ntohs(_addr.sin_port);*ip=inet_ntoa(_addr.sin_addr);}
public:InetAddr(const struct sockaddr_in &addr): _addr(addr){GetAddress(&_ip,&_port);}std::string Ip(){return _ip;}bool operator == (const InetAddr &addr){// if(_ip == addr._ip)if(_ip == addr._ip && _port == addr._port) // 方便测试{return true;}return false;}uint16_t Port(){return _port;}struct sockaddr_in Addr(){return _addr;}~InetAddr(){}
private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

Thread.hpp

#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{using func_t = std::function<void(std::string)>;class Thread{public:void Excute(){_func(_threadname);}public:Thread(func_t func, std::string name="none-name"): _func(func), _threadname(name), _stop(true){}static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!{Thread *self = static_cast<Thread *>(args);self->Excute();return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n){_stop = false;return true;}else{return false;}}void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;func_t _func;bool _stop;};
} // namespace ThreadModule#endif

ThreadPool.hpp

#include <iostream>
#include <vector>
#include <queue>
#include <pthread.h>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"using namespace ThreadModule;const static int gdefaultthreadnum = 5;// 日志
template <typename T>
class ThreadPool
{
private:void LockQueue(){pthread_mutex_lock(&_mutex);}void UnlockQueue(){pthread_mutex_unlock(&_mutex);}void ThreadSleep(){pthread_cond_wait(&_cond, &_mutex);}void ThreadWakeup(){pthread_cond_signal(&_cond);}void ThreadWakeupAll(){pthread_cond_broadcast(&_cond);}// 是要有的,必须是私有的ThreadPool(int threadnum = gdefaultthreadnum) : _threadnum(threadnum), _waitnum(0), _isrunning(false){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);LOG(INFO, "ThreadPool Construct()");}void InitThreadPool(){// 指向构建出所有的线程,并不启动for (int num = 0; num < _threadnum; num++){std::string name = "thread-" + std::to_string(num + 1);_threads.emplace_back(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name);LOG(INFO, "init thread %s done\n", name.c_str());}_isrunning = true;}void Start(){for (auto &thread : _threads){thread.Start();}}void HandlerTask(std::string name) // 类的成员方法,也可以成为另一个类的回调方法,方便我们继续类级别的互相调用!{LOG(INFO, "%s is running...\n", name.c_str());while (true){// 1. 保证队列安全LockQueue();// 2. 队列中不一定有数据while (_task_queue.empty() && _isrunning){_waitnum++;ThreadSleep();_waitnum--;}// 2.1 如果线程池已经退出了 && 任务队列是空的if (_task_queue.empty() && !_isrunning){UnlockQueue();break;}// 2.2 如果线程池不退出 && 任务队列不是空的// 2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后在退出// 3. 一定有任务, 处理任务T t = _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, "%s get a task", name.c_str());// 4. 处理任务,这个任务属于线程独占的任务t();// LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());}}// 复制拷贝禁用ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;ThreadPool(const ThreadPool<T> &) = delete;public:static ThreadPool<T> *GetInstance(){// 如果是多线程获取线程池对象下面的代码就有问题了!!// 只有第一次会创建对象,后续都是获取// 双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全if (nullptr == _instance) // 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象{LockGuard lockguard(&_lock);if (nullptr == _instance){_instance = new ThreadPool<T>();_instance->InitThreadPool();_instance->Start();LOG(DEBUG, "创建线程池单例\n");return _instance;}}LOG(DEBUG, "获取线程池单例\n");return _instance;}void Stop(){LockQueue();_isrunning = false;ThreadWakeupAll();UnlockQueue();}void Wait(){for (auto &thread : _threads){thread.Join();LOG(INFO, "%s is quit...", thread.name().c_str());}}bool Enqueue(const T &t){bool ret = false;LockQueue();if (_isrunning){_task_queue.push(t);if (_waitnum > 0){ThreadWakeup();}LOG(DEBUG, "enqueue task success\n");ret = true;}UnlockQueue();return ret;}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:int _threadnum;std::vector<Thread> _threads; // for fix, int tempstd::queue<T> _task_queue;pthread_mutex_t _mutex;pthread_cond_t _cond;int _waitnum;bool _isrunning;// 添加单例模式static ThreadPool<T> *_instance;static pthread_mutex_t _lock;
};template <typename T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;template <typename T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

Main.cc

#include<iostream>
#include"UdpServer.hpp"
#include"Log.hpp"
#include"LockGuard.hpp"
#include<memory>
#include"MessageRoute.hpp"void Usage(std::string proc)
{std::cout<<"Usage: \n\t"<<proc<<" lOCAL_port"<<std::endl;
}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(USAGE_ERROE);}EnableScreen();MessageRoute route;uint16_t port=atoi(argv[1]);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,\std::bind(&MessageRoute::Route,&route,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));usvr->InitServer();usvr->Start();return 0;
}

LockGuard.hpp

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__#include <iostream>
#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;
};#endif

Log.hpp

#pragma once#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"bool gIsSave = false;
const std::string logname = "log.txt";// 1. 日志是由等级的
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}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 GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 2. 日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";LockGuard lockguard(&lock);// pthread_mutex_lock(&lock);if (!issave){std::cout << message;}else{SaveFile(logname, message);}// pthread_mutex_lock(&lock); // bug??// std::cout << levelstr << " : " << timestr << " : " << filename << " : " << line << ":" << buffer << std::endl;
}// C99新特性__VA_ARGS__
#define LOG(level, format, ...)                                                \do                                                                         \{                                                                          \LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableFile()    \do                  \{                   \gIsSave = true; \} while (0)
#define EnableScreen()   \do                   \{                    \gIsSave = false; \} while (0)// 默认传递进来的参数都是整数
// void Test(int num, ...)
// {
//     va_list arg;
//     va_start(arg, num);//     while(num)
//     {
//         int data = va_arg(arg, int);
//         std::cout << "data: " << data << std::endl;
//         num--;
//     }//     va_end(arg); // arg = NULL
// }
http://www.dtcms.com/a/407036.html

相关文章:

  • python爬虫技术的运用与分析
  • 站长之家查询域名网站建设与维护 电子版
  • 行业商城网站建设多少钱网站建设软件的英文
  • CSS3 核心知识点与实战案例专栏
  • 企业网站建设中存在的问题分析黄冈做网站的公司哪家好
  • 单片机常见的编程语言有哪些?
  • 2.CSS3.(2).html
  • 什么是TCP/UDP/HTTP?
  • 解决银行任务池场景并发问题
  • 济南企业免费建站3d渲染网站建设
  • git实用命令
  • 面相对象程序设计与模块
  • 四川泰龙建设集团公司官方网站网站服务器 要求
  • 主频72Mhz采用高性能的32 位ARM®Cortex®-M0+ 内核的PY32C673芯片
  • Linux 内核编译,打包
  • android网站开发教程建筑行业网站开发
  • 网站建设一般收费广告设计公司图片
  • C++项目:仿muduo库高并发服务器---------LoopThreadPool模块和TcpServer模块的实现
  • S7-200 SMART GET/PUT 指令深度解析:从参数到故障排查(S7 协议的客户端 - 服务器通信)上
  • C++11之异常
  • 网站开发软硬件网站建设应注意什么
  • wordpress全站注明国外代理ip地址 免费
  • LightDM 深度解析:图形登录管理器的机制、TTY 映射与嵌入式调试实战
  • Dlib库 人脸应用实例 疲劳监测
  • 11.2. Linux 防火墙管理
  • VMware+RockyLinux+ikuai+docker+cri-docker+k8s 自用 实践笔记(三)
  • 基于全基因组做UGT基因家族,发Top期刊(纯生信)
  • 网店网站模板wordpress get_pages
  • 自己做视频网站的流程关键词排名点击软件首页
  • h5免费建站网站自动化采集