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

应用层自定义协议与序列化

应用层自定义协议与序列化

  • 应用层
  • 协议
  • 网络版计算器
  • 序列化和反序列化
    • 序列化
    • 反序列化
  • 重新理解read、write、recv、send和TCP为什么支持全双工
  • 代码结构
    • Jsoncpp
      • 特性
      • 安装
      • 序列化
        • 使用Json::Value的toStyledString方法
        • 使用Json::StreamWriter
        • 使用Json::FastWriter
      • 反序列化
        • 使用Json::Reader
  • 进程组
  • 会话
    • 创建会话
    • 会话ID
  • 控制终端
  • 作业控制
    • 关于任务的补充命令
  • 守护进程
  • 如何将服务守护进程化

应用层

  • 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

协议

  • 协议就是双方约定好的结构化的数据

网络版计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格;

约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 “序列化” 和 "反序列化

序列化和反序列化

序列化

其实就是把我们的结构化数据(比如一个结构体)变为一个大字符串。字符串的分隔符由我们自己定。

反序列化

把我们规定的字符串变为我们的结构化数据

重新理解read、write、recv、send和TCP为什么支持全双工

在这里插入图片描述
所以:

  • 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工

  • 这就是为什么一个 tcp sockfd 读写都是它的原因

  • 实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传输控制协议

代码结构

  • socket.hpp
#pragma once
#include "InetAddr.hpp"
#include "log.hpp"namespace SocketModule
{int mybacklog = 17;using namespace LogModule;// 虚基类class Socket{public:virtual void socketordie() = 0;virtual void bindordie(uint16_t port) = 0;virtual void listenordie() = 0;virtual std::shared_ptr<Socket> acceptordie(InetAddr *client) = 0;virtual void Close() = 0;virtual int getfd() = 0;virtual int Recive(std::string *buffer) = 0;virtual int Send(std::string pacakge) = 0;virtual int Connect(const std::string &server_ip, uint16_t port) = 0;void BuildTcpSocketMethod(uint16_t port){socketordie();bindordie(port);listenordie();}void BuildTcpClientSocketMethod(){socketordie();}};class TcpSocket : public Socket{public:TcpSocket() {}TcpSocket(int sockfd) : _sockfd(sockfd) {}~TcpSocket(){}int getfd() override{return _sockfd;}void socketordie() override{int n = socket(AF_INET, SOCK_STREAM, 0);if (n < 0){LOG(LogLevel::DEBUG) << "socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "socker success";_sockfd = n;}void bindordie(uint16_t port) override{InetAddr local(port);int n = bind(_sockfd, local.NetAddrPtr(), local.NetAddrLen());if (n < 0){LOG(LogLevel::DEBUG) << "bind error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";}int Recive(std::string *buffer) override{char buff[4096];int n = ::recv(_sockfd, buff, sizeof(buff) - 1, 0);if (n > 0){buff[n] = 0;}*buffer += buff;return n;}int Send(std::string pacakge) override{int n = send(_sockfd, pacakge.c_str(), pacakge.size(), 0);return n;}void listenordie() override{int n = listen(_sockfd, mybacklog);if (n < 0){LOG(LogLevel::DEBUG) << "listen error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "listen success";}void Close() override{close(_sockfd);}// 输出型参数吧客户端信息带出去// 返回值设置为Tcpsocket这样我们就可以继续在类内添加方法如果后续需要实现std::shared_ptr<Socket> acceptordie(InetAddr *client) override{sockaddr_in peer;socklen_t len = sizeof(peer);int n = accept(_sockfd, CONV(peer), &len);if (n < 0){LOG(LogLevel::WARNING) << "accept warning" << strerror(errno);return nullptr;}LOG(LogLevel::DEBUG) << "accept success";sleep(1);client->SetAddr(peer);return std::make_shared<TcpSocket>(n);}int Connect(const std::string &server_ip, uint16_t port) override{InetAddr server(server_ip, port);return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());}private:int _sockfd;};
}
  • servenetcal.hpp
#pragma once
#include"Socket.hpp"
using namespace SocketModule;
//实现回调函数调用上层
using func_t = std::function<void(std::shared_ptr<Socket> sockfd,InetAddr peer)>;
class TcpServe
{public:TcpServe(uint16_t port,func_t func):_port(port),_func(func),_isrunning(false),_listensocket(std::make_unique<TcpSocket>()){//此时我们的_listensocket_listensocket->BuildTcpSocketMethod(port);}void Start(){_isrunning=true;while(_isrunning){InetAddr client;auto lsocket=_listensocket->acceptordie(&client);if(lsocket==nullptr){LOG(LogLevel::DEBUG)<<"accept warning";continue;}LOG(LogLevel::DEBUG) << "accept success ..." << client.StringAddr();//创建子进程pid_t id=fork();if(id==0){//子进程//关闭自己的lisetsockfdLOG(LogLevel::DEBUG)<<_listensocket->getfd();_listensocket->Close();if(fork()>0){exit(OK);}//孙子进程_func(lsocket,client);exit(OK);}else if(id>0){//父进程//关闭自己的sockfd//LOG(LogLevel::DEBUG)<<lsocket->getfd();lsocket->Close();waitpid(id,nullptr,0);}else{exit(FORK_ERR);}}_isrunning=false;}~TcpServe(){}private:std::unique_ptr<Socket> _listensocket;uint16_t _port;func_t _func;bool _isrunning;
};
  • servenetcal.cpp
#include"servenetcal.hpp"
#include"protocol.hpp"
#include"cal.hpp"
#include"Daemon.hpp"
using namespace SocketModule;
int main(int argc, char *argv[])
{ENABLE_FILE_LOG_STRATEGY();if (argc != 2){std::cout << "Uage:" << argv[0] << "prot" << std::endl;return 0;}uint16_t port = std::stoi(argv[1]);LOG(LogLevel::DEBUG) << "服务器已经启动,已经是一个守护进程了" ;Daemon(0,0);//1.计算机std::unique_ptr<Cal> cal=std::make_unique<Cal>();//注册方法//2.协议std::unique_ptr<Protocol> pro=std::make_unique<Protocol>([&cal](Request req)->Response{return cal->Excute(req);});//3.服务层//注册方法std::unique_ptr<TcpServe> psvs = std::make_unique<TcpServe>(port, [&pro](std::shared_ptr<Socket> sockfd, InetAddr peer) {pro->GetRequest(sockfd,peer);  });//从tcp通信不断往上层回调psvs->Start();return 0;
}
{std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}void GetDataFromStdin(int *x, int *y, char *oper)
{std::cout << "Please Enter x: ";std::cin >> *x;std::cout << "Please Enter y: ";std::cin >> *y;std::cout << "Please Enter oper: ";std::cin >> *oper;
}// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{**加粗样式**if (argc != 3){Usage(argv[0]);exit(USAGE_ERR);}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);//此时client指向的是子类std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();client->BuildTcpClientSocketMethod();if (client->Connect(server_ip, server_port) != 0){// 失败std::cerr << "connect error" << std::endl;exit(CONNECT_ERR);}std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();std::string resp_buffer;// 连接服务器成功while (true){// 1. 从标准输入当中获取数据int x, y;char oper;GetDataFromStdin(&x, &y, &oper);// 2. 构建一个请求-> 可以直接发送的字符串std::string req_str = protocol->BuildRequestString(x, y, oper);//std::cout<<req_str<<std::endl;// std::cout << "-----------encode req string-------------" << std::endl;// std::cout << req_str << std::endl;// std::cout << "------------------------------------------" << std::endl;// 3. 发送请求int n=client->Send(req_str);if(n<0){LOG(LogLevel::DEBUG)<<"send eror";}// 4. 获取应答Response resp;bool res = protocol->GetResponse(client, resp_buffer, &resp);if(res == false)break;// 5. 显示结果resp.ShowResult();}client->Close();return 0;
}
  • common.hpp
#pragma once
#include <iostream>
#include <string.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include<pthread.h>
#define CONV(addr) ((struct sockaddr *)&addr)
enum ExitCode
{OK = 0,SOCKET_ERR,BIND_ERR,LISTEN_ERR,USAGE_ERR,CONNECT_ERR,FORK_ERR,ACCEPT_ERR,OPEN_ERR
};
// 用来防止拷贝的
class nocopy
{
public:nocopy() {}~nocopy() {}nocopy operator=(const nocopy &n) = delete;nocopy(const nocopy &n) = delete;
};
  • intaddr.hpp
#pragma once
#include"common.hpp"
class InetAddr
{
public:InetAddr(){}//网络转主机InetAddr(sockaddr_in sock){//_ip=inet_ntoa(_sockaddrin.sin_addr);// _port = ntohs(_addr.sin_port);// char ipbuffer[64];// inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));// _ip = ipbuffer;SetAddr(sock);}//主机转网络InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port){// 主机转网络memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);_addr.sin_port = htons(_port);}InetAddr(uint16_t port) : _port(port), _ip("0"){// 主机转网络memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;_addr.sin_addr.s_addr = INADDR_ANY;_addr.sin_port = htons(_port);}const struct sockaddr *NetAddrPtr(){return CONV(_addr);}~InetAddr(){}std::string Ip(){return _ip;}uint16_t Port(){return _port;}bool operator==(const InetAddr &peer){return _ip == peer._ip && _port == peer._port;}std::string StringAddr(){return _ip + ":" + std::to_string(_port);}socklen_t NetAddrLen(){return sizeof(_addr);}const struct sockaddr_in &NetAddr() { return _addr; }void SetAddr(sockaddr_in add){_addr=add;int port=ntohs(_addr.sin_port);char ipbuffer[64];inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));_ip=ipbuffer;}private:sockaddr_in _addr;std::string _ip;uint16_t _port;
};
  • protocol.hpp
#pragma once
#include <string>
#include <jsoncpp/json/json.h>
#include <string.h>
#include <iostream>
#include "Socket.hpp"
using namespace SocketModule;
class Request
{
public:Request(){}Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper){}std::string Serialize(){// _x = 10 _y = 20, _oper = '+'// "10" "20" '+' : 用空格作为分隔符// 进行序列化std::string ret;Json::Value root;root["a1"] = _x;root["a2"] = _y;root["operator"] = _oper;Json::FastWriter writer;ret = writer.write(root);return ret;}// {"x": 10, "y" : 20, "oper" : '+'}bool Deserialize(std::string &in){// "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'Json::Value root;Json::Reader reader;bool ok = reader.parse(in, root);if (!ok)return false;_x = root["a1"].asInt();_y = root["a2"].asInt();_oper = root["operator"].asInt();return true;}~Request() {}int X() { return _x; }int Y() { return _y; }char Oper() { return _oper; }private:int _x;int _y;char _oper; // + - * / % -> _x _oper _y -> 10 + 20
};// server -> client
class Response
{
public:Response() {}Response(int result, int code) : _result(result), _code(code){}std::string Serialize(){Json::Value root;root["res"] = _result;root["code"] = _code;Json::FastWriter wirter;return wirter.write(root);}bool Deserialize(std::string &in){Json::Value root;Json::Reader reader;bool ok = reader.parse(in, root);if (!ok)return false;_result = root["res"].asInt();_code = root["code"].asInt();return true;}~Response() {}void Set(int result, int code){_result = result;_code = code;}void ShowResult(){std::cout << "result:" << _result << " " << "code:" << _code<<std::endl;}private:int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值int _code;   // 0:sucess, 1,2,3,4->不同的运算异常的情况, 约定!!!!
};
const std::string sepf = "\r\n";using func2 = std::function<Response(Request)>;
class Protocol
{
public:Protocol(){}Protocol(func2 func5) : _func2(func5) {};std::string Encode(std::string mes){// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n// 应用层封装报头std::string ret;int len = mes.size();ret += std::to_string(len) + sepf + mes + sepf;return ret;}// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n// 5// 50// 50\r// 50\r\n// 50\r\n{"x": 10, "// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope//.....// packge故意是&// 1. 判断报文完整性// 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个bool Decode(std::string &buffer, std::string *packge){auto pos = buffer.find(sepf);if (pos == std::string::npos){return false;}std::string slen = buffer.substr(0, pos);int len = std::stoi(slen);int targetlen = slen.size() + 2 * sep.size() + len;if (buffer.size() < targetlen){return false;}// 这里一定有一条完整请求*packge = buffer.substr(pos + sep.size(), len);buffer.erase(0, targetlen);return true;}// 服务端将来会需要获得需求void GetRequest(std::shared_ptr<Socket> sockfd, InetAddr peer){std::string buffermessage;while (true){int n = sockfd->Recive(&buffermessage);//std::cout << buffermessage << std::endl;if (n > 0){std::string package;bool ret = Decode(buffermessage, &package);if (!ret)continue;//std::cout << package << std::endl;// 此时已经获得了一个完整的报文// 下面进行反序列化便于我们进行业务逻辑的处理Request request;bool ok = request.Deserialize(package);if (!ok)continue;// 下面业务处理交给上层处理Response reponse = _func2(request);// 把应答进行序列化std::string rep = reponse.Serialize();// 添加报头std::string reppackkge = Encode(rep);// 发送int n = sockfd->Send(reppackkge);if (n < 0){LOG(LogLevel::DEBUG) << "send erroe " << strerror(errno);}}else if (n == 0){LOG(LogLevel::INFO) << "client:" << peer.StringAddr() << "Quit!";break;}else{LOG(LogLevel::WARNING) << "client:" << peer.StringAddr() << ", recv error";break;}}}// 客户端获得// 将来buffer存的是从fd读出来的数据bool GetResponse(std::shared_ptr<Socket> &client, std::string &buffer, Response *ret){while (true){int n = client->Recive(&buffer);if (n > 0){// std::cout << "-----------reponse_buffer--------------" << std::endl;// std::cout << buffer << std::endl;// std::cout << "------------------------------------" << std::endl;std::string json_package;// 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取while (Decode(buffer, &json_package)){// 我敢100%保证,我一定拿到了一个完整的报文// {"x": 10, "y" : 20, "oper" : '+'} -> 你能处理吗?// 2. 请求json串,反序列化// std::cout << "-----------reponse_json--------------" << std::endl;// std::cout << json_package << std::endl;// std::cout << "------------------------------------" << std::endl;// std::cout << "-----------reponse_buffer--------------" << std::endl;// std::cout << buffer << std::endl;// std::cout << "------------------------------------" << std::endl;ret->Deserialize(json_package);}return true;}else if (n == 0){std::cout << "server quit " << std::endl;return false;}else{std::cout << "recv error" << std::endl;return false;}}}std::string BuildRequestString(int x, int y, char c){// 1. 构建一个完整的请求Request req(x, y, c);std::string ret1 = req.Serialize();return Encode(ret1);}~Protocol(){}private:func2 _func2;
};
  • clientnetcal.cpp
#include"protocol.hpp"
#include"Socket.hpp"using namespace SocketModule;void Usage(std::string proc)
{std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}void GetDataFromStdin(int *x, int *y, char *oper)
{std::cout << "Please Enter x: ";std::cin >> *x;std::cout << "Please Enter y: ";std::cin >> *y;std::cout << "Please Enter oper: ";std::cin >> *oper;
}// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(USAGE_ERR);}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);//此时client指向的是子类std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();client->BuildTcpClientSocketMethod();if (client->Connect(server_ip, server_port) != 0){// 失败std::cerr << "connect error" << std::endl;exit(CONNECT_ERR);}std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();std::string resp_buffer;// 连接服务器成功while (true){// 1. 从标准输入当中获取数据int x, y;char oper;GetDataFromStdin(&x, &y, &oper);// 2. 构建一个请求-> 可以直接发送的字符串std::string req_str = protocol->BuildRequestString(x, y, oper);//std::cout<<req_str<<std::endl;// std::cout << "-----------encode req string-------------" << std::endl;// std::cout << req_str << std::endl;// std::cout << "------------------------------------------" << std::endl;// 3. 发送请求int n=client->Send(req_str);if(n<0){LOG(LogLevel::DEBUG)<<"send eror";}// 4. 获取应答Response resp;bool res = protocol->GetResponse(client, resp_buffer, &resp);if(res == false)break;// 5. 显示结果resp.ShowResult();}client->Close();return 0;
}
  • mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexMoudle
{class Mutex{public:pthread_mutex_t *Get(){return &_mutex;}Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){pthread_mutex_lock(&_mutex);}void Unlock(){pthread_mutex_unlock(&_mutex);}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(MutexMoudle:: Mutex &mutex) : _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:MutexMoudle::Mutex _mutex;};
}
  • log.hpp
#pragma once
#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem> // C++17, 需要⾼版本编译器和-std=c++17
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
namespace LogModule
{std::string sep = "\r\n";using namespace MutexMoudle;// 定义日志的刷新方式class LogStrategy{public:virtual ~LogStrategy() = default;                     // 策略的构造函数virtual void SyncLog(const std::string &message) = 0; // 不同模式核⼼是刷};// 显示器刷新class ScreenStrtegy : public LogStrategy{public:~ScreenStrtegy() {};void SyncLog(const std::string &message) override{LockGuard lock(_mutex);std::cout << message << sep;}private:Mutex _mutex;};const std::string defaultpath = "/var/log/";const std::string defaultfile = "log.log";class FileLogStrategy : public LogStrategy{public:FileLogStrategy(std::string path = defaultpath, std::string file = defaultfile): _path(path), _file(file){LockGuard lock(_mutex);// 先构建我们的路径if (std::filesystem::exists(_path))return;try{std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}~FileLogStrategy() {}void SyncLog(const std::string &message) override{LockGuard lock(_mutex);std::string drc = _path + (_path.back() == '/' ? "" : "/") + _file;std::ofstream out(drc.c_str(), std::ios::app); // 追加⽅式if (!out.is_open())return;out << message << sep;out.close();}private:std::string _path;std::string _file;Mutex _mutex;};// ⽇志等级enum class LogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// ⽇志转换成为字符串std::string LogLevelToString(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";}}std::string GetCurtime(){time_t tm = time(nullptr);struct tm curr;localtime_r(&tm, &curr);char buffer[1024];snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900, curr.tm_mon, curr.tm_mday,curr.tm_hour, curr.tm_min, curr.tm_sec);return std::string(buffer);}// Log负责日志的策略选择以及刷新class Log{public:Log(){UseConsoleStrategy();}void UseConsoleStrategy(){_ptr = std::make_unique<ScreenStrtegy>();}void UseFileStrategy(){_ptr = std::make_unique<FileLogStrategy>();}// 改类负责形成完整的语句class logmessgge{public:logmessgge(LogLevel &type, std::string filename, int line, Log &log): _type(type),_curr_time(GetCurtime()),_pid(getpid()),_filename(filename),_line(line),_log(log){// 完成日志左边的写std::stringstream in;in<< "[" << _curr_time << "]" << " "<< "[" << LogLevelToString(_type) << "]"<<" "<< "[" << _pid << "]" << " "<< "[" << _filename << "]" << " "<< "[" << _line << "]" << " " << "-";_loginfo = in.str();}~logmessgge(){// 实现自动调用我们的刷新逻辑// 因此我们要传入我们的外部类Log_log._ptr->SyncLog(_loginfo);}template <typename T>logmessgge &operator<<(const T &data){// 支持重载std::stringstream in;in << data;_loginfo += in.str();return *this; // 为了支持连续进行输入}private:LogLevel _type;         // ⽇志等级std::string _curr_time; // ⽇志时间pid_t _pid;             //std::string _filename;  // 对应的⽂件名int _line;              // 对应的⽂件⾏号std::string _loginfo;Log &_log; // 外部类对象引用方便我们自动刷新};~Log() {}// Log(level)<<"hellowore"// 我们想要如上面调用我们的日志进行我们的()重载// 返回值写成拷贝返回// 临时对象这样我们进行日志写入会自动// 调用logmessge的析构函数进行刷新// 返回值为logmessage是为了调用我们的<<// 因此该函数就是支持把Log可以转化为logmessage类型//函数返回一个临时对象的引用时,//临时对象会在函数返回后立即被析构,导致返回的引用成为悬垂引用(Dangling Reference)//但是我们想要的是调用<<之后在结束因此//我们进行拷贝这样就延长了临时对象的生命周期//logmessgge operator()(LogLevel type, std::string filename, int line){return logmessgge(type, filename, line, *this);}private:std::unique_ptr<LogStrategy> _ptr;};// 全局的日志对象Log mylog;
// 调用operator()
//具体就是先调用()去构造logmessage的信息接着调用>>完成对右边部分的填充接着析构调用刷新
#define LOG(level) mylog(level, __FILE__, __LINE__)// 提供选择使⽤何种⽇志策略的⽅法
#define ENABLE_CONSOLE_LOG_STRATEGY() mylog.UseConsoleStrategy()
#define ENABLE_FILE_LOG_STRATEGY() mylog.UseFileStrategy()
}
  • cal.hpp
#pragma once
#include "protocol.hpp"
class Cal
{
public:Cal() {}Response Excute(Request &req){Response ret(0, 0);switch (req.Oper()){case '+':ret.Set(req.X() + req.Y(), 0);break;case '-':ret.Set(req.X() - req.Y(), 0);break;case '*':ret.Set(req.X() * req.Y(), 0);break;case '/':{int code = 0;if (req.Y() == 0){code = 1;ret.Set(0,1);}elseret.Set(req.X() / req.Y(), code);}break;case '%':{int code = 0;if (req.Y() == 0){code = 2;ret.Set(0,code);}elseret.Set(req.X() % req.Y(), code);}break;default:ret.Set(0, 3); // 非法操作break;}return ret;}~Cal() {}private:
};
  • daemon.hpp
#pragma once
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
#include "common.hpp"
using namespace LogModule;
// 第一个参数如果为0改变工作目录为根目录,
// 第二个参数为0代表把标准输入标准输出标准错误重定向到/dev/null
std::string workpath = "/dev/null";
void Daemon(int nochdir, int noclose)
{signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);pid_t id = fork();if (fork() > 0)exit(OK);// 子进程// 建立新会话并成为新会话的首进程组setsid();if (nochdir == 0){chdir("/");}if (noclose == 0){// du读写方式打开int fd = open(workpath.c_str(), O_RDWR);if (fd < 0){LOG(LogLevel::FATAL) << "open " << workpath << " errno";exit(OPEN_ERR);}dup2(fd,0);dup2(fd,1);dup2(fd,2);}else{close(0);close(1);close(2);}
}

Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字
符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各
种需要处理 JSON 数据的 C++ 项目中。

特性

  1. 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
  2. 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
  3. 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。
  4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便
    开发者调试。当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择。以下是对 Jsoncpp 中序列化和反序列化操作的详细介绍

安装


ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件
中。Jsoncpp 提供了多种方式进行序列化:

使用Json::Value的toStyledString方法
#include<jsoncpp/json/json.h>
#include <iostream>
#include <string>
#include<memory>
#include<sstream>struct Student
{std::string name;int age;
};int main()
{Student s1={"小王",18};Json::Value root;root["n"]=s1.name;root["a"]=s1.age;std::cout<<root.toStyledString()<<std::endl;return 0;
}
使用Json::StreamWriter
#include<jsoncpp/json/json.h>
#include <iostream>
#include <string>
#include<memory>
#include<sstream>struct Student
{std::string name;int age;
};int main()
{Student s1={"小王",18};Json::Value root;root["n"]=s1.name;root["a"]=s1.age;// std::cout<<root.toStyledString()<<std::endl;Json::StreamWriterBuilder wb;//std::unique_ptr<Json::StreamWriter> writer(wb.newStreamWriter()); std::stringstream ss;writer->write(root,&ss);std::cout<<ss.str()<<std::endl;return 0;
}
使用Json::FastWriter
#include <jsoncpp/json/json.h>
#include <iostream>
#include <string>
#include <memory>
#include <sstream>struct Student
{std::string name;int age;
};int main()
{Student s1 = {"小王", 18};Json::Value root;root["n"] = s1.name;root["a"] = s1.age;// std::cout<<root.toStyledString()<<std::endl;// Json::StreamWriterBuilder wb;//// std::unique_ptr<Json::StreamWriter> writer(wb.newStreamWriter());// std::stringstream ss;//  writer->write(root,&ss);//  std::cout<<ss.str()<<std::endl;// return 0;Json::FastWriter writer1;Json::StyledWriter writer2;std::cout<< writer1.write(root)<<"\n";std::cout<< writer2.write(root);}

反序列化

使用Json::Reader
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main() {
// JSON 字符串
std::string json_string = "{\"name\":\"张三\",
\"age\":30, \"city\":\"北京\"}";
// 解析 JSON 字符串
Json::Reader reader;
Json::Value root;
// 从字符串中读取 JSON 数据
bool parsingSuccessful = reader.parse(json_string,
root);
if (!parsingSuccessful) {
// 解析失败,输出错误信息
std::cout << "Failed to parse JSON: " <<
reader.getFormattedErrorMessages() << std::endl;
return 1;
}
// 访问 JSON 数据
std::string name = root["name"].asString();
int age = root["age"].asInt();
std::string city = root["city"].asString();
// 输出结果
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "City: " << city << std::endl;
return 0;
}

进程组

  • 之前我们提到了进程的概念, 其实每一个进程除了有一个进程 ID(PID)之外 还属于一个进程组。进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程 ID, 同样是一个正整数, 可以存放在 pid_t 数据类型中。

  • 每一个进程组都有一个组长进程。 组长进程的 ID 等于其进程 ID。我们可以通过 ps 命令看到组长进程的现象

[node@localhost code]$ ps -o pid,pgid,ppid,comm | cat
# 输出结果
PID PGID PPID COMMAND
2806 2806 2805 bash
2880 2880 2806 ps
2881 2880 2806 cat

从结果上看 ps 进程的 PID 和 PGID 相同, 那也就是说明 ps 进程是该进程组的组长进程, 该进程组包括 ps 和 cat 两个进程。

  • 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程
  • 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。注意:主要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终止无关.

会话

  • 刚刚我们谈到了进程组的概念, 那么会话又是什么呢? 会话其实和进程组息息相关,会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。每一个会话也有一个会话 ID(SID)
    在这里插入图片描述

创建会话

可以调用 setseid 函数来创建一个会话, 前提是调用进程不能是一个进程组的组长.

#include <unistd.h>
/*
*功能:创建会话
*返回值:创建成功返回 SID, 失败返回-1
*/
pid_t setsid(void);

该接口调用之后会发生:

  • 调用进程会变成新会话的会话首进程。 此时, 新会话中只有唯一的一个进程
  • 调用进程会变成进程组组长。 新进程组 ID 就是当前调用进程 ID
  • 该进程没有控制终端。 如果在调用 setsid 之前该进程存在控制终端, 则调用之后会切断联系
  • 需要注意的是: 这个接口如果调用进程原来是进程组组长, 则会报错, 为了避免这种情况, 我们通常的使用方法是先调用 fork 创建子进程, 父进程终止, 子进程继续执行, 因为子进程会继承父进程的进程组 ID, 而进程 ID 则是新分配的, 就不会出现错误的情况。

会话ID

  • 上边我们提到了会话 ID, 那么会话 ID 是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程 ID 的单个进程, 那么我们可以将会话首进程的进程 ID 当做是会话 ID。注意:会话 ID 在有些地方也被称为 会话首进程的进程组 ID, 因为会话首进程总是一个进程组的组长进程, 所以两者是等价的。

控制终端

在 UNIX 系统中,用户通过终端登录系统后得到一个 Shell 进程,这个终端成为 Shell进程的控制终端。控制终端是保存在 PCB 中的信息,我们知道 fork 进程会复制 PCB中的信息,因此由 Shell 进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系,我们在下边详细介绍一下:

  • 一个会话可以有一个控制终端,通常会话首进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端。
  • 建立与控制终端连接的会话首进程被称为控制进程。
  • 一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。
  • 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组。
  • 无论何时进入终端的中断键(ctrl+c)或退出键(ctrl+\),就会将中断信号发送给前台进程组的所有进程。
  • 如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程(会话首进程)。
    这些特性的关系如下图所示:
    在这里插入图片描述

作业控制

作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。
Shell 分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运⾏一个前台作业和任意多个后台作业,这称为作业控制。

关于任务的补充命令

  • jobs:查看系统当前的后台任务
  • fg 任务号 把任务放到前台
  • bg 任务号 启动后台任务
  • ctrl+c: 终止前台任务
  • ctrl+z: 暂停前台任务,切回后台任务,同时bask成为前台

守护进程

  • 本质是让子进程去建立会话,特殊的孤儿进程。
#pragma once
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
#include "common.hpp"
using namespace LogModule;
// 第一个参数如果为0改变工作目录为根目录,
// 第二个参数为0代表把标准输入标准输出标准错误重定向到/dev/null
std::string workpath = "/dev/null";
void Daemon(int nochdir, int noclose)
{signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);pid_t id = fork();if (fork() > 0)exit(OK);// 子进程// 建立新会话并成为新会话的首进程组setsid();if (nochdir == 0){chdir("/");}if (noclose == 0){// du读写方式打开int fd = open(workpath.c_str(), O_RDWR);if (fd < 0){LOG(LogLevel::FATAL) << "open " << workpath << " errno";exit(OPEN_ERR);}dup2(fd,0);dup2(fd,1);dup2(fd,2);}else{close(0);close(1);close(2);}
}

如何将服务守护进程化

#include"servenetcal.hpp"
#include"protocol.hpp"
#include"cal.hpp"
#include"Daemon.hpp"
using namespace SocketModule;
int main(int argc, char *argv[])
{ENABLE_FILE_LOG_STRATEGY();if (argc != 2){std::cout << "Uage:" << argv[0] << "prot" << std::endl;return 0;}uint16_t port = std::stoi(argv[1]);LOG(LogLevel::DEBUG) << "服务器已经启动,已经是一个守护进程了" ;Daemon(0,0);//1.计算机std::unique_ptr<Cal> cal=std::make_unique<Cal>();//注册方法//2.协议std::unique_ptr<Protocol> pro=std::make_unique<Protocol>([&cal](Request req)->Response{return cal->Excute(req);});//3.服务层//注册方法std::unique_ptr<TcpServe> psvs = std::make_unique<TcpServe>(port, [&pro](std::shared_ptr<Socket> sockfd, InetAddr peer) {pro->GetRequest(sockfd,peer);  });//从tcp通信不断往上层回调psvs->Start();return 0;
}

在这里插入图片描述

http://www.xdnf.cn/news/6896.html

相关文章:

  • 毛泽东(井冈山)词三篇
  • 英语学习5.17
  • 电子电路:到底该怎么理解电容器的“通交流阻直流”?
  • 高频面试题(含笔试高频算法整理)基本总结回顾120
  • Conda 完全指南:从环境管理到工具集成
  • 飞帆控件 post or get it when it has get
  • FastMCP:为大语言模型构建强大的上下文和工具服务
  • C++类与对象--1 特性一:封装
  • vue.js 更新数据时,出现数据更新,界面没有更新的情况【普通对象,不包含数组】
  • NBA足球赛事直播源码体育直播M33模板赛事源码
  • B站锁定三倍速(自用)
  • Dubbo:Docker部署Zookeeper、Dubbo Admin的详细教程和SpringBoot整合Dubbo的实战与演练
  • gem5-gpu教程 第十章 关于topology 的Mesh network
  • USB基础知识
  • Selenium测试框架快速搭建
  • 多模态学习( 二 )——Token与Embedding的区别:从文本到向量空间的旅程
  • 手动实现 Transformer 模型
  • QT+EtherCAT 主站协议库—SOEM主站
  • 什么是差分传输?
  • 免费代理IP服务有哪些隐患?如何安全使用?
  • 智力题整理汇总版
  • 高频面试题(含笔试高频算法整理)基本总结回顾61
  • 浅谈前端架构设计与工程化
  • 【降维】t-SNE
  • 腾讯 CodeBuddy 杀入 AI 编程赛道,能否撼动海外工具霸主地位?
  • 力扣-283-移动零
  • 中文分词与数据可视化01
  • 板对板连接器极限测试:振动、温差与盐雾三重挑战下的生存实录
  • 我的世界模组开发——特征(2)
  • 【Linux内核】设备驱动之块设备介绍