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

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

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

  • 应用层
  • 协议
  • 网络版计算器
  • 序列化和反序列化
    • 序列化
    • 反序列化
  • 重新理解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;
}

在这里插入图片描述

相关文章:

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

应用层自定义协议与序列化 应用层协议网络版计算器序列化和反序列化序列化反序列化 重新理解read、write、recv、send和TCP为什么支持全双工代码结构Jsoncpp特性安装序列化使用Json::Value的toStyledString方法使用Json::StreamWriter使用Json::FastWriter 反序列化使用Json::R…...

2025春训第二十场

问题 B: 狗是啥呀 题目描述 在神秘的地狱深处&#xff0c;有着一种神秘的犬类生物&#xff0c;据传这种生物长了x个脑袋&#xff0c;并且具有强大的生命力。由于见过它的人全都下落不明&#xff0c;至今没有人知道它的真面目。 一位勇士为了斩杀这奇怪的生物&#xff0c;来到地…...

分糖果--思维+while判断

1.从左到右只考虑右边一遍&#xff0c;再从右到左考虑左边一遍&#xff0c;相当于左右考虑了 2.然后关键是1遍不一定行&#xff0c;while循环直到成功 https://www.luogu.com.cn/problem/B4091 #include<bits/stdc.h> using namespace std; #define N 100011 typedef …...

[system-design] ByteByteGo_Note Summary

目录 通信协议 REST API 与 GraphQL gRPC 如何工作&#xff1f; 什么是Webhook&#xff1f; 如何提高应用程序接口的性能&#xff1f; HTTP 1.0 -> HTTP 1.1 -> HTTP 2.0 -> HTTP 3.0 (QUIC) SOAP vs REST vs GraphQL vs RPC 代码优先与应用程序接口优先 HTT…...

Flask项目实践:构建功能完善的博客系统(含评论与标签功能)

引言 在Python Web开发领域&#xff0c;Flask以其轻量级、灵活性和易用性赢得了众多开发者的青睐。本文将带您从零开始构建一个功能完善的博客系统&#xff0c;包含文章发布、评论互动和标签分类等核心功能。通过这个实战项目&#xff0c;您不仅能掌握Flask的核心技术&#xf…...

Python爬虫实战:获取1688商品信息

在电商领域&#xff0c;获取1688商品信息对于市场分析、竞品研究、用户体验优化等至关重要。1688作为国内领先的B2B电商平台&#xff0c;提供了丰富的商品资源。通过Python爬虫技术&#xff0c;我们可以高效地获取1688商品的详细信息&#xff0c;包括商品名称、价格、图片、描述…...

Canva 推出自有应用生成器以与 Bolt 和 Lovable 竞争

AI 目前是一个巨大的市场,每个人都想从中分一杯羹。 即使是 Canva,这个以拖放图形设计而闻名的流行设计平台,也在其 Canva Create 2025 活动中发布了自己版本的代码生成器,加入了 AI 竞赛。 但为什么一个以设计为先的平台会提供代码生成工具呢? 乍看之下,这似乎有些不…...

多平台屏幕江湖生存指南

UniApp 屏幕适配大师:多平台屏幕江湖生存指南 屏幕江湖:尺寸混战 屏幕适配就像是应对不同体型的客人:从迷你的手机屏,到标准的平板,再到巨大的电视屏幕,你的应用必须有如武林高手般的适应力。 ┌──────────────────────────────────…...

BootCDN介绍(Bootstrap主导的前端开源项目免费CDN加速服务)

文章目录 BootCDN前端开源项目CDN加速服务全解析什么是BootCDN技术原理与架构CDN技术基础BootCDN架构特点1. 全球分布式节点网络2. 智能DNS解析系统3. 高效缓存管理机制4. 自动同步更新机制5. HTTPS和HTTP/2协议支持 BootCDN的核心优势速度与稳定性开源免费资源丰富度技术规范遵…...

LeetCode 153. 寻找旋转排序数组中的最小值:二分查找法详解及高频疑问解析

文章目录 问题描述算法思路&#xff1a;二分查找法关键步骤 代码实现代码解释高频疑问解答1. 为什么循环条件是 left < right 而不是 left < right&#xff1f;2. 为什么比较 nums[mid] > nums[right] 而不是 nums[left] < nums[mid]&#xff1f;3. 为什么 right …...

刷leetcodehot100返航版--二叉树

二叉树理论基础 二叉树的种类 满二叉树和完全二叉树&#xff0c;二叉树搜索树 满二叉树 如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 节点个数2^n-1【n为树的深度】 完全二叉树 在完全二叉树…...

「Mac畅玩AIGC与多模态41」开发篇36 - 用 ArkTS 构建聚合搜索前端页面

一、概述 本篇基于上一节 Python 实现的双通道搜索服务&#xff08;聚合 SearxNG 本地知识库&#xff09;&#xff0c;构建一个完整的 HarmonyOS ArkTS 前端页面。用户可在输入框中输入关键词&#xff0c;实时查询本地服务 http://localhost:5001/search?q...&#xff0c;返…...

【LINUX操作系统】生产者消费者模型(下):封装、信号量与环形队列

1.封装、完善基于阻塞队列的productor-consumer module 前文中我们封装了自己的Mutex 【LINUX操作系统】线程同步与互斥-CSDN博客 按照老规矩&#xff0c;现在我们对同步与互斥的理解更进一步了&#xff0c;现在把这种面向过程的语言封装成面向对象的写法 1.1 封装条件变量 #p…...

项目管理学习-CSPM-4考试总结

前言 经过两个月左右时间的学习&#xff0c;今天&#xff08;2025年5月17日&#xff09;参加了CSPM-4的考试&#xff0c;仿佛回到了2011年参加软考高项的时候。中午12点考完出来后&#xff0c;手都是酸酸的。不过整体感觉还可以&#xff0c;和预想的差不多。CSPM-4的考试一共有…...

自己手写tomcat项目

一&#xff1a;Servlet的原理 在Servlet(接口中)有&#xff1a; 1.init():初始化servlet 2.getServletConfig()&#xff1a;获取当前servlet的配置信息 3.service():服务器&#xff08;在HttpServlet中实现&#xff0c;目的是为了更好的匹配http的请求方式&#xff09; 4.g…...

C语言—再学习(结构体)

一、建立结构体 用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体。 struct Student { int num; //学号char name[20]; //名字为字符串char sex; //性别int age; //年纪float score; //分数char addr[30]; 地址为字符…...

SpringBoot--自动配置原理详解

为什么要学习自动配置原理&#xff1f; 原因&#xff1a;在实际开发中&#xff0c;我们经常会定义一些公共的组件&#xff0c;提供各个团队来使用&#xff0c;为了使用方便&#xff0c;我们经常会将公共的组件自定义成starter&#xff0c;如果想自定义starter&#xff0c;必须…...

MiInsertPageInFreeList函数分析和MmFreePagesByColor数组的关系

第一部分&#xff1a; Color MI_GET_COLOR_FROM_LIST_ENTRY(PageFrameIndex, Pfn1); ColorHead &MmFreePagesByColor[ListName][Color]; 第二部分&#xff1a; #define MI_GET_COLOR_FROM_LIST_ENTRY(index,pfn) \ ((ULONG)(((pfn)->…...

Windows/MacOS WebStorm/IDEA 中开发 Uni-App 配置

文章目录 前言1. 安装 HBuilder X2. WebStorm/IDEA 安装 Uniapp Tool 插件3. 配置 Uniapp Tool 插件4. 运行 Uni-App 项目 前言 前端开发人员对 WebStorm 一定不陌生&#xff0c;但有时需要开发 Uni-App 的需求&#xff0c;就必须要采用 HBuilder X&#xff0c;如果不习惯 HBu…...

redisson分布式锁实现原理归纳总结

Redisson 分布式锁的实现原理主要依赖于 Redis 的 Hash 数据结构、Lua 脚本、发布订阅机制以及看门狗&#xff08;Watchdog&#xff09;机制&#xff0c;以下是核心要点总结&#xff1a; 1. 核心原理 • 互斥性与可重入性&#xff1a; 通过 Redis 的 Hash 数据结构保存锁的持…...

Ubuntu 添加系统调用

实验内容 通过内核编译法添加一个不用传递参数的系统调用&#xff0c;其功能可自定义。 &#xff08;1&#xff09;添加系统调用号&#xff0c;系统会根据这个号找到syscall_table中的相应表项。具体做法是在syscall_64.tbl文件中添加系统调用号和调用函数的对应关系。 &#…...

Olib 2.2.0 | 免费开源软件,无需注册登录即可从ZLibrary下载多语言电子书

Olib是一款专为书籍爱好者设计的免费开源软件&#xff0c;它允许用户无需注册或登录即可从ZLibrary高速下载各种语言的电子书。该软件支持上百种语言的电子书下载&#xff0c;非常适合需要多语言资源的读者和研究人员使用。Olib的操作界面非常直观&#xff0c;使得书籍的搜索与…...

c++动态链接库

1. 生成动态链接库 首先实现一个动态链接库的代码 // example.cpp #include <iostream> void sayHello() {std::cout << "Hello from shared library!" << std::endl; }int add(int a, int b) {return a b; }// example.h #pragma once void sa…...

HelloWorld

HelloWorld 新建一个java文件 文件后缀名为 .javahello.java【注意】系统可能没有显示文件后缀名&#xff0c;我们需要手动打开 编写代码 public class hello {public static void main(String[] args) {System.out.print(Hello,World)} }编译 javac java文件&#xff0c;会生…...

SVGPlay:一次 CodeBuddy 主动构建的动画工具之旅

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 背景与想法 我一直对 SVG 图标的动画处理有浓厚兴趣&#xff0c;特别是描边、渐变、交互等效果能为图标增添许…...

SLAM定位常用地图对比示例

序号 地图类型 概述 1 格栅地图 将现实环境栅格化,每一个栅格用 0 和 1 分别表示空闲和占据状态,初始化为未知状态 0.5 2 特征地图 以点、线、面等几何特征来描绘周围环境,将采集的信息进行筛选和提取得到关键几何特征 3 拓扑地图 将重要部分抽象为地图,使用简单的图形表示…...

强化学习中,frames(帧)和 episodes(回合)

在强化学习中&#xff0c;frames&#xff08;帧&#xff09;和 episodes&#xff08;回合&#xff09;是两个不同的概念&#xff1a; 1. 定义差异 Frame&#xff08;帧&#xff09;&#xff1a; 表示智能体与环境交互的单个时间步&#xff08;step&#xff09;&#xff0c;例如…...

HCIP第六次作业

一、拓扑图 二、需求 1、使用PreVal策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2、使用AS_Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3、配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4、使用Local Preference策略&#xff0c;确保R1通过R2…...

高频面试题(含笔试高频算法整理)基本总结回顾110

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…...

数据湖与数据仓库融合:Hudi、Iceberg、Delta Lake 实践对比

在实时与离线一体化的今天,数据湖与数据仓库边界不断融合,越来越多企业选用如 Hudi、Iceberg、Delta Lake 等开源方案实现统一的数据存储、计算、分析平台。本篇将围绕以下关键点,展开实战对比与解决方案分享: ✅ 实时写入能力 ✅ ACID 保证 ✅ 增量数据处理能力 ✅ 流批一…...

OGG 更新表频繁导致进程中断,见鬼了?非也!

大家好&#xff0c;这里是 DBA学习之路&#xff0c;专注于提升数据库运维效率。 目录 前言问题描述问题分析解决方案后续 前言 最近几周一直遇到一个 OGG 问题&#xff0c;有一张表已更新就会中断 OGG 同步进程&#xff0c;本文记录一下分析过程以及解决方案。 问题描述 昨天…...

C++学习-入门到精通-【7】类的深入剖析

C学习-入门到精通-【7】类的深入剖析 类的深入剖析 C学习-入门到精通-【7】类的深入剖析一、Time类的实例研究二、组成和继承三、类的作用域和类成员的访问类作用域和块作用域圆点成员选择运算符(.)和箭头成员选择运算符(->)访问函数和工具函数 四、具有默认实参的构造函数重…...

非易失性存储技术综合对比:EEPROM、NVRAM、NOR Flash、NAND Flash和SD卡

非易失性存储技术综合对比&#xff1a;EEPROM、NVRAM、NOR Flash、NAND Flash和SD卡 读写性能对比 存储类型读取速度写入速度随机访问能力最小操作单位NVRAM极快(~10ns)极快(~10ns)极优(字节级)字节EEPROM中等(~100ns)慢(~5ms/字节)优(字节级)字节NOR Flash快(~50ns)慢(~5ms/…...

数字化转型- 数字化转型路线和推进

数字化转型三个阶段 百度百科给出的企业的数字化转型包括信息化、数字化、数智化三个阶段 信息化是将企业在生产经营过程中产生的业务信息进行记录、储存和管理&#xff0c;通过电子终端呈现&#xff0c;便于信息的传播与沟通。数字化通过打通各个系统的互联互通&#xff0c;…...

ARM (Attention Refinement Module)

ARM模块【来源于BiSeNet】&#xff1a;细化特征图的注意力&#xff0c;增强重要特征并抑制不重要的特征。 Attention Refinement Module (ARM) 详解 ARM (Attention Refinement Module) 是 BiSeNet 中用于增强特征表示的关键模块&#xff0c;它通过注意力机制来细化特征图&…...

符合Python风格的对象(对象表示形式)

对象表示形式 每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方 式。Python 提供了两种方式。 repr()   以便于开发者理解的方式返回对象的字符串表示形式。str()   以便于用户理解的方式返回对象的字符串表示形式。 正如你所知&#xff0c;我们要实现_…...

AtCoder AT_abc406_c [ABC406C] ~

前言 除了 A 题&#xff0c;唯一一道一遍过的题。 题目大意 我们定义满足以下所有条件的一个长度为 N N N 的序列 A ( A 1 , A 2 , … , A N ) A(A_1,A_2,\dots,A_N) A(A1​,A2​,…,AN​) 为波浪序列&#xff1a; N ≥ 4 N\ge4 N≥4&#xff08;其实满足后面就必须满足这…...

多指标组合策略

该策略(MultiConditionStrategy)是一种基于多种技术指标和市场条件的交易策略。它通过综合考虑多个条件来生成交易信号,从而决定买入或卖出的时机。 以下是对该策略的详细分析: 交易逻辑思路 1. 条件1:星期几和价格变化判断 - 该条件根据当前日期是星期几以及价格的变化…...

系统架构-大数据架构设计

基础介绍 三大挑战&#xff1a; 如何处理非结构化和半结构化数据如何探索大数据复杂性、不确定性特征描述的刻画方法及大数据的系统建模数据异构性与决策异构性的关系对大数据知识发现与管理决策的影响 架构特征&#xff1a; 鲁棒性&#xff08;稳定性&#xff09;和容错性…...

R语言空间数据处理入门教程

我的课程《R语言空间数据处理入门教程》已重新恢复课程售卖&#xff0c;有需要的读者可以学习。 &#x1f447;点击下方链接&#xff08;文末“阅读原文”可直达&#xff09;&#xff0c;立即开启你的空间数据之旅&#xff1a; https://www.bilibili.com/cheese/play/ss13775…...

QT+EtherCAT 主站协议库—SOEM主站

SOEM 是 Simple Open EtherCAT Master Library 的缩写&#xff0c;是瑞典 rt-lab 提供 的一个开源 EtherCAT 主站协议库 。 SOEM 库使用 C 语言编写&#xff0c;可以在 windows 以及 Linux 平台上运行&#xff0c;并也可以方便地移植到嵌入式平台上。 SOEM 支持 CoE &#xff0…...

Java-反射(Reflection)

一&#xff1a;概述 &#xff08;1&#xff09;出现背景 &#xff08;2&#xff09;解决方案 &#xff08;3&#xff09;使用场景 业务开发用的少&#xff0c;框架使用的多&#xff0c;业务反射被认为是动态语言的关键 &#xff08;4&#xff09;与原方法对比 &#xff08;5…...

第一次经历项目上线

这几天没写csdn&#xff0c;因为忙着项目上线的问题&#xff0c;我这阶段改了非常多的前端bug哈哈哈哈&#xff0c;说几个比较好的bug思想&#xff01; 这个页面算是我遇到的比较大的bug&#xff0c;因为我一开始的逻辑都写好了&#xff0c;询价就是在点击快递公司弹出弹框的时…...

基于C#的MQTT通信实战:从EMQX搭建到发布订阅全解析

MQTT(Message Queueing Telemetry Transport) 消息队列遥测传输&#xff0c;在物联网领域应用的很广泛&#xff0c;它是基于Publish/Subscribe模式&#xff0c;具有简单易用&#xff0c;支持QoS&#xff0c;传输效率高的特点。 它被设计用于低带宽&#xff0c;不稳定或高延迟的…...

DeepSeek超大模型的高效训练策略

算力挑战 训练DeepSeek此类千亿乃至万亿级别参数模型,对算力资源提出了极高要求。以DeepSeek-V3为例,其基础模型参数量为67亿,采用专家混合(MoE)架构后实际激活参数可达几百亿。如此规模的模型远超单张GPU显存容量极限,必须借助分布式并行才能加载和训练。具体挑战主要包…...

【论文阅读】人脸修复(face restoration ) 不同先验代表算法整理

转眼做人脸复原(face restoration)算法也一段时间了&#xff0c;根据自己的记忆整理一下自己的一些看法&#xff0c;算作个人记录&#xff0c;当然如果有人愿意分享自己的看法也是极好的。先挂下文章链接&#xff0c;下一篇在写总结。 一、前述 人脸修复(face restoration)任…...

最小二乘法拟合平面(线性回归法、梯度下降、PCA法)

参考笔记&#xff1a; Open3D 最小二乘拟合平面&#xff08;直接求解法&#xff09;【2025最新版】_python open3d已知平面方程绘制平面-CSDN博客 目录 1.前言 2.线性回归法 2.1 模型假设 2.2 定义误差函数 2.3 求偏导并解方程 2.4 解方程 2.5 案例演示 2.5.1 手工计…...

数组名既可作为指针也可作为变量名

在C语言中&#xff0c;数组名在不同的上下文中既可以作为指向数组首个元素的指针&#xff0c;也可以代表整个数组&#xff0c;这是由C语言的设计和语法规则决定的&#xff0c;下面我来详细解释一下。 1. 数组名作为指向首元素的指针 在大多数情况下&#xff0c;当数组名出现在…...

MySQL相关

1.多表查询关键点在哪 &#x1f4d6; 1️⃣ 明确关联关系 先搞清楚多表之间的关联关系&#xff1a; 一对一&#xff08;1:1&#xff09; 一对多&#xff08;1:N&#xff09; 多对多&#xff08;M:N&#xff09; 比如&#xff1a; 一个课程对应一个教室&#xff08;1:1&am…...

Axure制作可视化大屏动态滚动列表教程

在可视化大屏设计中&#xff0c;动态滚动列表是一种常见且实用的展示方式&#xff0c;能够有效地展示大量信息。本文将详细介绍如何使用Axure制作一个动态滚动的列表展示模块。 一、准备工作 打开Axure软件&#xff1a;确保你已经安装并打开了Axure RP软件。创建新项目&#x…...