Mutex.hpp
cpp
#pragma once
#include <iostream>
#include <mutex>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
pthread_mutex_t *Get()
{
return &_lock;
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard
{
public:
LockGuard(Mutex *_mutex):_mutexp(_mutex)
{
_mutexp->Lock();
}
~LockGuard()
{
_mutexp->Unlock();
}
private:
Mutex *_mutexp;
};
Logger.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <filesystem> // C++17 文件操作
#include <fstream>
#include <ctime>
#include <unistd.h>
#include <memory>
#include <sstream>
#include "Mutex.hpp"
// 规定出场景的日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
// 日志转换成为字符串
std::string Level2String(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "Debug";
case LogLevel::INFO:
return "Info";
case LogLevel::WARNING:
return "Warning";
case LogLevel::ERROR:
return "Error";
case LogLevel::FATAL:
return "Fatal";
default:
return "Unknown";
}
}
// 根据时间戳,获取可读性较强的时间信息
// 20XX-08-04 12:27:03
std::string GetCurrentTime()
{
// 1. 获取时间戳
time_t currtime = time(nullptr);
// 2. 如何把时间戳转换成为20XX-08-04 12:27:03
struct tm currtm;
localtime_r(&currtime, &currtm);
// 3. 转换成为字符串
char timebuffer[64];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
currtm.tm_year + 1900,
currtm.tm_mon + 1,
currtm.tm_mday,
currtm.tm_hour,
currtm.tm_min,
currtm.tm_sec);
return timebuffer;
}
// 策略模式,策略接口
// 1. 刷新的问题 -- 假设我们已经有了一条完整的日志,string->设备(显示器,文件)
// 基类方法
class LogStrategy
{
public:
// 不同模式核心是刷新方式的不同
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &logmessage) = 0;
};
// 控制台日志策略,就是日志只向显示器打印,方便我们debug
// 显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
~ConsoleLogStrategy()
{
}
void SyncLog(const std::string &logmessage) override
{
{
LockGuard lockguard(&_lock);
std::cout << logmessage << std::endl;
}
}
private:
// 显示器也是临界资源,保证输出线程安全
Mutex _lock;
};
// 默认路径和日志名称
const std::string logdefaultdir = "log";
const static std::string logfilename = "test.log";
// 文件日志策略
// 文件刷新
class FileLogStrategy : public LogStrategy
{
public:
// 构造函数,建立出来指定的目录结构和文件结构
FileLogStrategy(const std::string &dir = logdefaultdir,
const std::string filename = logfilename)
: _dir_path_name(dir), _filename(filename)
{
LockGuard lockguard(&_lock);
if (std::filesystem::exists(_dir_path_name))
{
return;
}
try
{
std::filesystem::create_directories(_dir_path_name);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << "\r\n";
}
}
// 将一条日志信息写入到文件中
void SyncLog(const std::string &logmessage) override
{
{
LockGuard lockguard(&_lock);
std::string target = _dir_path_name;
target += "/";
target += _filename;
// 追加方式
std::ofstream out(target.c_str(), std::ios::app); // append
if (!out.is_open())
{
return;
}
out << logmessage << "\n"; // out.write
out.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _dir_path_name; // log
std::string _filename; // hello.log => log/hello.log
Mutex _lock;
};
// 具体的日志类
// 1. 定制刷新策略
// 2. 构建完整的日志
class Logger
{
public:
Logger()
{
}
void EnableConsoleLogStrategy()
{
_strategy = std::make_unique<ConsoleLogStrategy>();
}
void EnableFileLogStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
// 内部类,实现RAII风格的日志格式化和刷新
// 这个LogMessage,表示一条完整的日志对象
class LogMessage
{
public:
// RAII风格,构造的时候构建好日志头部信息
LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
: _curr_time(GetCurrentTime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
// stringstream不允许拷贝,所以这里就当做格式化功能使用
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "]"
<< " - ";
_loginfo = ss.str();
}
// 重载 << 支持C++风格的日志输入,使用模版,表示支持任意类型
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
// RAII风格,析构的时候进行日志持久化,采用指定的策略
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time; // 日志时间
LogLevel _level; // 日志等级
pid_t _pid; // 进程pid
std::string _filename;
int _line;
std::string _loginfo; // 一条合并完成的,完整的日志信息
Logger &_logger; // 引用外部logger类, 方便使用策略进行刷新
};
// 故意拷贝,形成LogMessage临时对象,后续在被<<时,会被持续引用,
// 直到完成输入,才会自动析构临时LogMessage,至此也完成了日志的显示或者刷新
// 同时,形成的临时对象内包含独立日志数据
// 未来采用宏替换,进行文件名和代码行数的获取
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{
}
private:
// 写入日志的策略
std::unique_ptr<LogStrategy> _strategy;
};
// 定义全局的logger对象
Logger logger;
// 使用宏,可以进行代码插入,方便随时获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
// 提供选择使用何种日志策略的方法
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()
InetAddr.hpp
cpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include "Logger.hpp"
using namespace std;
#define Conv(addr) ((struct sockaddr *)&addr)
class InetAddr
{
private:
void Net2Host()
{
_port = ntohs(_addr.sin_port);
// _ip = inet_ntoa(_addr.sin_addr);
char ipbuffer[64];
inet_ntop(AF_INET, &(_addr.sin_addr.s_addr), ipbuffer, sizeof(ipbuffer));
_ip = ipbuffer;
}
void Host2Net()
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
// _addr.sin_addr.s_addr = inet_addr(_ip.c_str());
inet_pton(AF_INET, _ip.c_str(), &(_addr.sin_addr.s_addr));
}
public:
InetAddr(){}
// 默认ip为INADDR_ANY(0.0.0.0)
InetAddr(uint16_t port, const string ip = "0.0.0.0")
: _ip(ip),
_port(port)
{
Host2Net();
}
InetAddr(struct sockaddr_in &addr)
{
_addr = addr;
Net2Host();
}
void Init(const struct sockaddr_in peer)
{
_addr = peer;
Net2Host();
}
struct sockaddr *Addr()
{
return Conv(_addr);
}
string IP()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
bool operator==(const InetAddr &addr)
{
return _ip == addr._ip && _port == addr._port;
}
socklen_t Length()
{
return sizeof(_addr);
}
string ToString()
{
return _ip + "-" + to_string(_port);
}
~InetAddr()
{
}
private:
// 网络风格地址
struct sockaddr_in _addr;
// 主机风格地址
string _ip;
uint16_t _port;
};
Socket.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include<functional>
#include "Logger.hpp"
#include "InetAddr.hpp"
using namespace std;
static int gbacklog = 16; // 设置默认的backlog
static const int gsockfd = -1;
// 设置错误码
enum
{
OK,
CREATE_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR
};
// 定义一个抽象类,父类中定义算法的骨架,将某些步骤的具体实现延迟到子类中
class Socket
{
public:
// 编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,
// 所以基类的析构函数加了vialtual修饰,派生类的析构函数就构成重写
// 基类必须有虚析构函数
virtual ~Socket() {}
// 纯虚函数强制派生类重写虚函数
virtual void CreatSocket() = 0;
virtual void BindSocket(int port) = 0;
virtual void ListenSocket() = 0;
virtual shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
virtual bool Connect(InetAddr &peer) = 0;
virtual int SockFd() = 0;
virtual void Close() = 0;
virtual ssize_t Recv(std::string *out) = 0;
virtual ssize_t Send(const std::string &in) = 0;
public:
void BuildListenSocketMethod(int port)
{
CreatSocket();
BindSocket(port);
ListenSocket();
}
void BuildClientSocketMethod()
{
CreatSocket();
}
};
class TcpSocket : public Socket
{
public:
TcpSocket() : _sockfd(gsockfd)
{
}
TcpSocket(int sockfd) : _sockfd(sockfd)
{
}
// override帮助用户检测是否重写
void CreatSocket() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "creat socket error";
exit(CREATE_ERR);
}
LOG(LogLevel::DEBUG) << "creat socket success";
}
void BindSocket(int port) override
{
InetAddr local(port);
if (bind(_sockfd, local.Addr(), local.Length()) != 0)
{
LOG(LogLevel::FATAL) << "bind socket error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind socket success";
}
void ListenSocket() override
{
if (listen(_sockfd, gbacklog) != 0)
{
LOG(LogLevel::FATAL) << "listen socket error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen socket success";
}
shared_ptr<Socket> Accept(InetAddr *clientaddr) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int clientfd = accept(_sockfd, (sockaddr *)&peer, &len);
if (clientfd < 0)
{
LOG(LogLevel::FATAL) << "accept error";
return nullptr;
}
clientaddr->Init(peer);
return make_shared<TcpSocket>(clientfd);
}
bool Connect(InetAddr &peer) override
{
if (connect(_sockfd, peer.Addr(), peer.Length()) != 0)
{
LOG(LogLevel::FATAL) << "connect error";
return false;
}
LOG(LogLevel::DEBUG) << "connect " << peer.ToString() << " success";
return true;
}
int SockFd() override
{
return _sockfd;
}
void Close() override
{
if (_sockfd >= 0)
close(_sockfd);
}
// 今天重点放在读,通过读,理解序列和反序列和自定义协议的过程
ssize_t Recv(std::string *out) override
{
// 只读一次
char buffer[1024];
ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
*out += buffer; // 故意+=
}
return n;
}
ssize_t Send(const std::string &in)
{
return send(_sockfd, in.c_str(), in.size(), 0);
}
~TcpSocket() {}
private:
int _sockfd;
};
Protocol.hpp
cpp
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
using namespace std;
// 协议就是双方约定好的结构化的数据
// Requst:客户端发送的请求
class Request
{
public:
Request()
{
_x = _y = _oper = 0;
}
// 对要发送的数据进行序列化
bool Serialize(string *out)
{
// 1. 构建 Json::Value 对象
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
// 2. 使用 StreamWriterBuilder
Json::StreamWriterBuilder writer; // StreamWriter 的工厂
writer["emitUTF8"] = true; // 允许输出UTF-8
*out = Json::writeString(writer, root);
if (out->empty())
return false;
return true;
}
// 对要接受的数据进行反序列化
bool Deserialize(string &s)
{
// 解析JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取JSON 数据
bool ret = reader.parse(s, root);
if (!ret)
return false;
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
int X()
{
return _x;
}
int Y()
{
return _y;
}
char Oper()
{
return _oper;
}
~Request() {}
//不做private:,方便客户端访问
//private:
int _x;
int _y;
char _oper;
};
// Response:服务器的应答
class Response
{
public:
Response()
{
_result = _code = 0;
}
bool Serialize(string *out)
{
// 1. 构建 Json::Value 对象
Json::Value root;
root["result"] = _result;
root["code"] = _code;
// 2. 使用 StreamWriterBuilder
Json::StreamWriterBuilder writer; // StreamWriter 的工厂
writer["emitUTF8"] = true; // 允许输出UTF-8
*out = Json::writeString(writer, root);
if (out->empty())
return false;
return true;
}
// 对要接受的数据进行反序列化
bool Deserialize(string &s)
{
// 解析JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取JSON 数据
bool ret = reader.parse(s, root);
if (!ret)
return false;
_result = root["result"].asInt();
_code = root["code"].asInt();
return true;
}
void SetResult(int r)
{
_result = r;
}
void SetCode(int c)
{
_code = c;
}
void Print()
{
cout << _result << "[" << _code << "]" << endl;
}
~Response() {}
private:
int _result;
int _code; // 判断_result是否可信
};
// 定义json中的分隔符
static const string sep = "\r\n";
class Protocol
{
public:
// 包装jsonstr便于后续解析
// jsonstr -> len\r\njsonstr\r\n
static string Package(const string &jsonstr)
{
if (jsonstr.empty())
return string();
int jsonstr_len = jsonstr.size();
return to_string(jsonstr_len) + sep + jsonstr + sep;
}
// 检查长度字符串是否符合标准
static bool DigitSafeCheck(const std::string str)
{
for (int i = 0; i < str.size(); i++)
{
if (!(str[i] >= '0' && str[i] <= '9'))
return false;
}
return true;
}
// 收到的报文有任意种可能
// len\r\njsonstr\r\n
// len\r\njsonstr\r\nlen\r\njsonstr\r\n
// len\r\njsonstr\r\nlen\r
// len\r\n
// len\r\njs
// len\r
// len
// origin_str: 从网络中读取上来的字符串,输入输出
// package: 输出参数,如果有完整的json报文,就返回
// UnPack对收到的报文进行解析以获得完整的json字符串
static int UnPack(string &origin_str, string *package)
{
//解析工作完成时返回jsonstr的长度,没有完成时返回0 解析出错返回-1
if (!package)
return -1;
auto pos=origin_str.find(sep);// 从左向右
if(pos==string::npos)
return 0;
//此时,我们至少获得了jsonstr的长度
string json_len=origin_str.substr(0,pos);
if(!DigitSafeCheck(json_len))
return -1;
int length=stoi(json_len);
// 如果我得到了当前报文的长度
// 根据协议,我可以推测出,一个完整报文的长度是多少
// len\r\njsonstr\r\n
int target_len=json_len.size()+sep.size()*2+length;
//如果没有一个完整的报文,就返回继续读,直至获得完整的的报文
if(origin_str.size()<target_len)
return 0;
////////////////////////////////////////////////////////////
//截止目前,可以确定origin_str内部,一定有一个完整的报文请求!且我们没有对origin_str做任何修改
*package=origin_str.substr(pos+sep.size(),length);
origin_str.erase(0, target_len);
return package->size();
}
};
Parse.hpp
cpp
#pragma once
#include <functional>
#include "Protocol.hpp"
#include "Logger.hpp"
using handler_t = function<Response(Request &req)>;
// 只负责对报文进行各种解析工作
class Parser
{
public:
Parser(handler_t handler)
: _handler(handler)
{
}
//解析报文
string Parse(string &inbuffer)
{
LOG(LogLevel::DEBUG) << "inbuffer: \r\n"<< inbuffer;
string send_str;
// 接受到的字符串可能包含多条报文请求
while (true)
{
std::string jsonstr;
// 1. 解析报文
int n = Protocol::UnPack(inbuffer, &jsonstr);
//解析出错,直接退出
if (n < 0)
exit(0);
// 未获得一条完整的报文或已经将inbuffer内所有完整报文处理完毕,退出循环
else if (n == 0)
break;
else
{
//解析出的一条完整jsonstr
LOG(LogLevel::DEBUG) << "jsonstr: \r\n" << jsonstr;
Request req;
// 2. 反序列化
if (!req.Deserialize(jsonstr))
{
return string();
}
// 3. 根据response, 处理具体的业务
Response resp = _handler(req);
// 4. 对resp在进行序列化
string resp_json;
if (!resp.Serialize(&resp_json))
{
return string();
}
// 5. 打包
send_str += Protocol::Package(resp_json);
}
}
//若未获得一条完整的报文,返回的是空串
//若已经将inbuffer内所有完整报文处理完毕,返回的是打包好的全部答复
return send_str;
}
~Parser() {}
private:
handler_t _handler;
};
Calculator.hpp
cpp
#pragma once
#include "Protocol.hpp"
#include <iostream>
#include <string>
class Calculator
{
public:
Calculator() {}
//Request进,Response出
Response Exec(Request &req)
{
Response resp;
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());
break;
case '*':
resp.SetResult(req.X() * req.Y());
break;
case '/':
{
if (req.Y() == 0)
{
resp.SetCode(1); // 1:div 0
}
else
{
resp.SetResult(req.X() / req.Y());
}
}
break;
case '%':
{
if (req.Y() == 0)
{
resp.SetCode(2); // 2:mod 0
}
else
{
resp.SetResult(req.X() % req.Y());
}
}
break;
default:
resp.SetCode(3); // 3: 非法操作
break;
}
return resp;
}
~Calculator() {}
};
TcpServer.hpp
cpp
#pragma once
#include <iostream>
#include "Socket.hpp"
#include "InetAddr.hpp"
using callback_t = function<string(string &)>;
class TcpServer
{
public:
TcpServer(int port, callback_t cb)
: _port(port),
_cb(cb),
_listensocket(make_unique<TcpSocket>())
{
_listensocket->BuildListenSocketMethod(_port);
}
void HandlerRequest(shared_ptr<Socket> socket, InetAddr &peer)
{
string buffer;
while (true)
{
ssize_t n = socket->Recv(&buffer);
if (n > 0)
{
// 协议相关!!!
// 1. 你怎么确定buffer里面有至少一个完整的报文呢?
// 2. 如何把这个完整的报文,交给上层呢?
// 3. 上层拿到了一个报文,该如何处理呢??
LOG(LogLevel::DEBUG) << peer.ToString() << "# " << buffer;
// 如何处理(检测? 解包? 序列..)收到的数据,和底层tcpserver没有关系
string send_str = _cb(buffer);
//如果返回的send_str是空串,则说明读到的inbuffer中尚未包含一条完整的报文,需继续读
if (send_str.empty())
continue;
socket->Send(send_str);
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.ToString() << " quit, me too!";
break;
}
else
{
LOG(LogLevel::DEBUG) << peer.ToString() << " read error, quit!";
break;
}
}
socket->Close();
}
void Run()
{
while (true)
{
InetAddr clientaddr;
auto clientsocket = _listensocket->Accept(&clientaddr);
if (!clientsocket)
continue;
LOG(LogLevel::DEBUG) << "获取一个新连接: " << clientaddr.ToString()
<< ", sockfd : " << clientsocket->SockFd();
//如果不fork,主线程去处理请求时其他客户端来发起连接就没有办法及时接受了
if (fork() == 0)
{
_listensocket->Close();
HandlerRequest(clientsocket, clientaddr);
exit(0);
}
clientsocket->Close();
}
}
~TcpServer() {}
private:
int _port;
unique_ptr<Socket> _listensocket;
callback_t _cb;
};
Server.cpp
cpp
//三个互相解耦的模块分别对应OSI中上三层中不同的层级
#include "Calculator.hpp" // 业务 应用层
#include "Parser.hpp" // 报文解析,序列反序列化等 表示层
#include "TcpServer.hpp" // 网络通信开断连接 会话层
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " localport" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
EnableConsoleLogStrategy();
// 计算器对象
unique_ptr<Calculator> cal = make_unique<Calculator>();
// 协议解析模块
unique_ptr<Parser> parser = make_unique<Parser>([&cal](Request &req)->Response{
return cal->Exec(req);
});
// 网络通信模块
uint16_t serverport = stoi(argv[1]);
std::unique_ptr<TcpServer> tsock = make_unique<TcpServer>(
serverport, [&parser](string &inbuffer)->string{
return parser->Parse(inbuffer);
});
tsock->Run();
return 0;
}
Client.cpp
cpp
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "InetAddr.hpp"
#include "Protocol.hpp"
using namespace std;
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " serverip serverport" << std::endl;
}
// ./Client serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
EnableConsoleLogStrategy();
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
// 创建socket
unique_ptr<Socket> sockptr = make_unique<TcpSocket>();
sockptr->BuildClientSocketMethod();
InetAddr server(serverport, serverip);
// 连接
if (sockptr->Connect(server))
{
string inbuffer;
while (true)
{
// 1. 构建请求
Request req;
cout << "Please Enter X: ";
cin >> req._x;
cout << "Please Enter Y: ";
cin >> req._y;
cout << "Please Enter Operator: ";
cin >> req._oper;
// 2. 序列化
string jsonstr;
req.Serialize(&jsonstr);
cout << "jsonstr: \r\n"
<< jsonstr << endl;
// 3. 打包
// len\r\njsonstr\r\n
string sendstr = Protocol::Package(jsonstr);
cout << "sendstr:\r\n"
<< sendstr << endl;
// 4. 发送
sockptr->Send(sendstr);
// 5. 接收
sockptr->Recv(&inbuffer);
// 6. 报文解析
string package;
int n = Protocol::UnPack(inbuffer, &package);
if (n > 0)
{
Response resp;
// 7. 反序列化
bool r = resp.Deserialize(package);
if (r)
{
resp.Print();
}
}
}
}
return 0;
}
Makefile
cpp
.PHONY:all
all:Server Client
Client::Client.cpp
g++ -o $@ $^ -std=c++17 -ljsoncpp
Server:Server.cpp
g++ -o $@ $^ -std=c++17 -ljsoncpp
.PHONY:clean
clean:
rm -f Server Client


守护进程化
Daemon.hpp
cpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
void Daemon()
{
// 1. 忽略可能引起程序异常退出的信号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
// 2. 让进程不要是组长
if (fork() > 0)
exit(0);
// 3. 每一个进程都有自己的CWD,建议将当前进程的CWD 更改成为/根目录
chdir("/");
// 4. 将自己设计成为新的会话,后面的代码其实是子进程在走
setsid();
// 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了 两种方法
// a. 关闭 b. 标准输入,标准输出,标准错误 -> 重定向 -> /dev/null(最佳实践)
int fd = open("/dev/null", O_RDWR);
if(fd >= 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
}
}

server.cpp中作些许改变,其他不变


输出的日志文件
