网络 :序列和反序列化

网络 :序列和反序列化

  • (一)序列和反序列 概念
  • (二)实例
    • 1. 封装socket 接口
    • 2. 制定协议(用于实现序列和反序列化)
    • 3. 计算(实现计算器功能)
    • 4. 服务器(将上面所有的类功能调用起来)
    • 5. 服务端
    • 6.客户端
  • (三) Json
    • 1.使用例子
    • 2.使用json实现网络计算器的序列和反序列
  • 四) OSI7层模型

(一)序列和反序列 概念

序列化:

  • 序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。这种形式通常是字节流或其他结构化数据格式,便于在网络中传递或保存到持久化介质(如磁盘文件)。通过序列化,对象可以在不同的环境中被安全地传递或长期保存。
    在实际应用中,序列化的核心目标是确保对象的完整性和可传递性。这意味着无论是在同一台机器上的不同进程之间还是跨网络的不同设备之间,都可以无损地传递对象的数据状态

反序列化:

  • 反序列化则是序列化的逆操作,即将已序列化的数据重新还原为原始的对象实例。这一过程涉及解析字节流并依据其中保存的对象状态及描述信息重建对象。反序列化使得程序能够恢复先前保存的对象状态,从而继续执行后续逻辑或完成特定任务。

序列化和反序列化就是用户规定的一种约定 ,他是用户层上的 “约定” ,这个约定由程序员自己来规定,而通信双方都要遵守这种协议。

(二)实例

下面我们通过实现一个简易的 通信计算器 来更好的理解序列和反序列化。

1. 封装socket 接口

这里的通信时基于TCP协议。封装socket接口方便客户端和使用端更好的使用。

Sock.hpp头文件封装套接字接口 , Log.hpp是一个日志系统。

#pragma Once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>#include "Log.hpp"Log lg;enum
{SOCK_ERR = 1,BIND_ERR,LISTEN_ERR,S
};class Sock
{
public:void Socket(){socketfd = socket(AF_INET, SOCK_STREAM, 0);if (socketfd < 0){lg(Fatal, "socket errno : %d ,%s", errno, strerror(errno));exit(SOCK_ERR);}}void Bind(uint16_t &port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(socketfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind errno : %d ,%s", errno, strerror(errno));exit(BIND_ERR);}}void Listen(){int n = listen(socketfd, 10);if (n < 0){lg(Fatal, "listen errno : %d ,%s", errno, strerror(errno));exit(LISTEN_ERR);}}//输出型参数                    输出型参数int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in remote;socklen_t len = sizeof(remote);int newfd = accept(socketfd, (struct sockaddr *)&remote, &len);if (newfd < 0){lg(Warning, "accept errno : %d ,%s", errno, strerror(errno));return -1;}char buffip[64];inet_ntop(AF_INET, &remote.sin_addr, buffip, sizeof(buffip));*clientip = buffip;*clientport = ntohs(remote.sin_port);return newfd;}bool Connect(std::string &serverip, uint16_t &serverport){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = connect(socketfd, (const struct sockaddr *)&server, sizeof(server));if (n < 0){lg(Warning, "connect errno : %d ,%s", errno, strerror(errno));return false;}return true;}int Getfd(){return socketfd;}void Close(){close(socketfd);}private:int socketfd;
};

Log.hpp 日志系统代码如下:

#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// PrintMethod
#define Screen 1
#define Onefile 2
#define Muchfile 3// leve,指的是日志等级,等级不同处理的方式也不同
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define LogFile "log.txt"
class Log
{
public:Log(){path = "./log/";_PrintMethod = Screen;}// 用户指定打印方式void AppontPrint(int PrintMethod){_PrintMethod = PrintMethod;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (_PrintMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Muchfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string filename = path + logname;int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd < 0){return;}int n = write(fd,logtxt.c_str(),logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level);   // "logtxt.Info/Fatal"printOneFile(filename,logtxt);}void operator()(int level, const char *format, ...){// 自定义部分time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[1024];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// 默认添加部分va_list s;va_start(s, format);char rightbuffer[1024];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);char logtxt[2024];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);printLog(level, logtxt);}~Log(){}private:std::string path; // 将路径信息打印到某个路径文件下int _PrintMethod; // 打印的方法(打印到屏幕或文件或多个文件等)
};

2. 制定协议(用于实现序列和反序列化)

Request类实现的是将 运算的数据 和 运算符 进行序列和反序化。他有三个成员(x_,y_表示运算数据,op_表示运算符)。

const std::string blank_sep = " ";   //用于分割 x op y
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;return true;}bool Deserialize(std::string &in) // x op y  , 反序列将 字符串 构造成对象{// 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);return true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << "=?? "<<std::endl;}
public:int x_;int y_;char op_; // + - * /
};

Response类是将 计算后的结构 和 错误码 进行序列和反序列化 。该类有两个成员(result_表示计算的结果, exitcode_表示错误码用于判断结果的合理性)。

const std::string blank_sep = " ";   //用于分割 "result exitcode"
class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;return true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{// 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);return true;}public:int result_;int exitcode_;
};

如果只是将 数据以"x op y"或者 "result exitcode"的形式进行数据传输 不好拿出传输的数据,因为你不知道传输的数据是否完整。所以我再给这个有效数据加上 一个包头。

const std::string protocol_sep = "\n";  //用于分割 "len\n"x op y\n// "len"\n"x op y"\n  或者 "len"\n"result exitcode"\n ,  len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size)              // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len);  // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}

Encode()函数给有效数据 加上 len\n"有效数据"\n ,其中len表示有效数据的大小。Decode()函数则是 取出传输数据中的有效数据,同时会删除传输数据里已经确定的有效数据(即报文)。

总代码如下:

Protocol.hpp头文件

#pragma once
#include <iostream>const std::string blank_sep = " ";   //用于分割 x op y
const std::string protocol_sep = "\n";  //用于分割 "len\n"x op y\n// "len"\n"x op y"\n  或者 "len"\n"result exitcode"\n ,  len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size)              // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len);  // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;return true;}bool Deserialize(std::string &in) // x op y  , 反序列将 字符串 构造成对象{// 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);return true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << std::endl;}
public:int x_;int y_;char op_; // + - * /
};class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;return true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{// 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);return true;}public:int result_;int exitcode_;
};

3. 计算(实现计算器功能)

ServerCal.hpp头文件

ServerCal类实现的是计算器的功能 ,服务器得到客户端传输过来的数据后,通过调用该类的函数对数据计算处理 ,并将结果返回给客户端。

#pragma Once
#include <iostream>
#include "Protocol.hpp"enum
{Div_Zero = 1,Mod_Zero,Other_Oper
};class ServerCal
{
public:Response Headler(Request &req){Response res(0, 0);switch (req.op_){case '+':res.result_ = req.x_ + req.y_;break;case '-':res.result_ = req.x_ - req.y_;break;case '*':res.result_ = req.x_ * req.y_;break;case '/':{if (req.y_ == 0)res.exitcode_ = Div_Zero;elseres.result_ = req.x_ / req.y_;}break;case '%':{if (req.y_ == 0)res.exitcode_ = Mod_Zero;elseres.result_ = req.x_ % req.y_;}break;default:res.exitcode_ = Other_Oper;break;}return res;}std::string Calculator(std::string &package) // 将报文处理成我们想要的结构{std::string content;bool n = Decode(package, &content);if (!n)return ""; // Decode 出现问题返回一个空字符串Request req;req.Deserialize(content); // 将 x op y 反序列化成 对象。Response res = Headler(req); // 处理 request 对象,将 数据转换成 response对象content = "";res.Serialize(&content);   // 将 response 序列化成字符串content = Encode(content); // "len\n"result exitcodereturn content;}
};

服务端得到客户端传过来的报文后,调用Calculator()函数,将 计算双方 转换成 结果返回给服务器 。

4. 服务器(将上面所有的类功能调用起来)

TcpServer.hpp头文件

TcpServer类中有三个成员函数,其中 Sock listenfd_表示监听文件描述符(注意它是Sock类型),port_表示服务器端口号 , callback_表示回调函数 (该函数是ServerCal中的Calculator函数,由服务端传递,这样做可以有效解耦)。

#pragma Once
#include <unistd.h>
#include <signal.h>
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"extern Log lg;using func_t = std::function<std::string(std::string &package)>; // 回调函数// 传 ServerCal.hpp类中的 Calulator函数class TcpServer
{
public:TcpServer(uint16_t port, func_t callback): port_(port), callback_(callback){}// 服务器初始化 TCP协议void InitServer(){listenfd_.Socket();listenfd_.Bind(port_);listenfd_.Listen();lg(Info, " InitServer done errno:%d ,%s", errno, strerror(errno));}void Start(){signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);while (true){// 连接客户端std::string clientip;uint16_t clientport;int sockfd = listenfd_.Accept(&clientip, &clientport);if (sockfd < 0){lg(Warning, "accept errno:&d ,%s", errno, strerror(errno));continue;}lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);if (fork() == 0) // 子进程来执行任务{listenfd_.Close(); // 子进程关闭 listenfdstd::string inbuffer_stream;  //传输的数据存放处while (true){char inbuff[128];ssize_t n = read(sockfd, inbuff, sizeof(inbuff));if (n > 0){inbuff[n] = 0;inbuffer_stream += inbuff;lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 将读到的数据 写入到客户端中 , 直到缓存区的数据读取完。while (true){std::string info = callback_(inbuffer_stream); // "len\n"result exitcodeif (info.empty())break;lg(Debug, "debug, response:\n%s", info.c_str());lg(Debug, "debug:\n%s", inbuffer_stream.c_str());ssize_t w = write(sockfd, info.c_str(), info.size());}}else if (n <= 0)break;}exit(0); // 执行完任务的子进程退出}// 父进程 -----close(sockfd);}}private:Sock listenfd_;uint16_t port_;func_t callback_; // 回调函数
};

提供创造子进程来执行任务。

5. 服务端

ServerCal.cc头文件

#pragma Once
#include "ServerCal.hpp"
#include "TcpServer.hpp"#include <iostream>
#include <sys/socket.h>
void Usage(std::string proc)
{std::cout << "Usage :" << proc << "  serverport" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return -1;}uint16_t port = std::stoi(argv[1]);ServerCal cal;TcpServer* tcp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));tcp->InitServer();tcp->Start();return 0;
}

6.客户端

Client.cc头文件

#pragma Once
// #include <time.h>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"void Usage(std::string proc)
{std::cout << "Usage :" << proc << " serverip serverport" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return -1;}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];//连接服务器Sock sockfd;sockfd.Socket();sockfd.Connect(ip, port);// ------const std::string opers = "+-*/%=-=&^"; //操作符的范围srand(time(nullptr));  int cnt = 5;std::string buff_stream;  //用于存放传输的数据while (cnt--){std::cout << "------------- 测试开始---cnt: " << cnt << std::endl;int x = rand() % 100 + 1;int y = rand() % 100;usleep(66666);char op = opers[rand() % opers.size()];//发送数据给服务器std::string content;  Request req(x, y, op);req.Serialize(&content); // 序列化content = Encode(content);req.Print();ssize_t n = write(sockfd.Getfd(), content.c_str(), content.size());if (n < 0){std::cerr << "write fail" << std::endl;}// std::cout << "这是最新的发出去的请求 "  << "\n" ;// write(sockfd.Getfd(), content.c_str(), content.size());// 读取数据char inbuff[1280];n = read(sockfd.Getfd(), inbuff, sizeof(inbuff));if (n > 0){inbuff[n] = 0;buff_stream += inbuff;// std::cout << buff_stream << std::endl;std::string info;bool r = Decode(buff_stream, &info);if (!r){break;}Response res;res.Deserialize(info);std::cout << res.result_ << "  " << res.exitcode_ << std::endl;}else{break;}std::cout << "----------------------------------------" << std::endl;sleep(1);}return 0;
}

运行效果图:

在这里插入图片描述

(三) Json

自己定义序列化和反序列化过于繁琐,我们可以调用其他的库来完成序列化和反序列化。
比如: Json、Protobuf 。

下面我们提供Json库来完成序列和反序列化。

1.使用例子

提供调用下面命令来现在Json库。

yum install -y jsoncpp-devel

使用该库要引入<jsoncpp/json/json.h>头文件

实例一:

#include<iostream>
#include<jsoncpp/json/json.h>
int main()
{Json::Value part1;part1["haha"] = "haha";part1["hehe"] = "hehe";Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["desc"] = "this is a + oper";root["test"] = part1;Json::FastWriter w;//Json::StyleWriter w;std::string res = w.write(root);std::cout << res << std::endl;return 0;
}

在这里插入图片描述

实例二:

int main()
{Json::Value part1;part1["haha"] = "haha";part1["hehe"] = "hehe";Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["desc"] = "this is a + oper";root["test"] = part1;Json::FastWriter w;std::string res = w.write(root);sleep(3);Json::Value v;Json::Reader r;r.parse(res, v);int x = v["x"].asInt();int y = v["y"].asInt();char op = v["op"].asInt();std::string desc = v["desc"].asString();Json::Value temp = v["test"];std::cout << x << std::endl;std::cout << y << std::endl;std::cout << op << std::endl;std::cout << desc << std::endl;return 0;
}

在这里插入图片描述

2.使用json实现网络计算器的序列和反序列

makefile

.PHONY:all
all:server clientFlag=#-DMySelf=1server:ServerCal.ccg++ -o $@ $^ -std=c++11 -ljsoncpp $(Flag)
client:Client.ccg++ -o $@ $^ -std=c++11  -ljsoncpp $(Flag).PHONY:clean
clean:rm -f server client

protocol.hpp

#pragma once
#include <iostream>
#include<jsoncpp/json/json.h>//#define MySelf 1const std::string blank_sep = " ";   //用于分割 x op y
const std::string protocol_sep = "\n";  //用于分割 "len\n"x op y\n// "len"\n"x op y"\n  或者 "len"\n"result exitcode"\n ,  len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size)              // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len);  // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{
#ifdef MySelf        std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;
#elseJson::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::StyledWriter w;*out = w.write(root);#endifreturn true;}bool Deserialize(std::string &in) // x op y  , 反序列将 字符串 构造成对象{
#ifdef MySelf        // 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);
#elseJson::Value root;Json::Reader r;r.parse(in,root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();#endifreturn true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << "=?? "<<std::endl;}
public:int x_;int y_;char op_; // + - * /
};class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{
#ifdef MySelf                std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;
#elseJson::Value root;root["result"] = result_;root["exit"] = exitcode_;Json::StyledWriter w;*out = w.write(root);#endifreturn true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{
#ifdef MySelf                // 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);
#elseJson::Value root;Json::Reader r;r.parse(in,root);result_ = root["result"].asInt();exitcode_ = root["exit"].asInt();#endifreturn true;}public:int result_;int exitcode_;
};

运行效果:
在这里插入图片描述

四) OSI7层模型

在这里插入图片描述

我们计算功能(ServerCal.hpp)相当于应用层,制定的序列化和反序列化(Protocol.hpp)相当于表示层,TcpServer.hpp服务器相当于会话层。

这就是为什么上面这三层是由我们自己定义的统称为应用层。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/82670.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LiveQing 视频点播流媒体 RTMP 推流服务功能:搭建 RTMP 视频流媒体服务详细指南

LiveQing视频点播流媒体RTMP推流服务功能&#xff1a;搭建RTMP视频流媒体服务详细指南 一、流媒体服务搭建二、推流工具准备三、创建鉴权直播间四、获取推流地址五、配置OBS推流六、推流及播放七、获取播放地址7.1 页面查看视频源地址7.2 接口查询 八、相关问题解决8.1 大疆无人…

UE5 Niagara 如何让四元数进行旋转

Axis Angle中&#xff0c;X,Y,Z分别为旋转的轴向&#xff0c;W为旋转的角度&#xff0c;在这里旋转角度不需要除以2&#xff0c;因为里面已经除了&#xff0c;再将计算好的四元数与要进行旋转的四元数进行相乘&#xff0c;结果就是按照原来的角度绕着某一轴向旋转了某一角度

【微服务】SpringBoot 对接飞书审批流程使用详解

目录 一、前言 二、前置准备 2.1 开通企业飞书账户 2.2 确保账户具备多维表操作权限 2.3 获取飞书开放平台文档 2.4 创建应用 2.5 发布应用 2.6 应用添加操作权限 2.7 获取SDK 三、审批流程对接过程 3.1 配置流程审批定义(流程审批模型) 3.2 自定义应用添加审批AP…

主键与唯一键详解:概念、区别与面试要点

主键与唯一键详解:概念、区别与面试要点 一、核心概念解析 1.1 主键(Primary Key) 主键是数据库表中用于唯一标识每一行记录的列或列组合,具有以下核心特性: 唯一性:主键值在整个表中必须唯一,不允许重复非空性:主键列不允许包含NULL值不可变性:主键值一旦确立,原则…

前端面试准备-1

1.NodeJS的优缺点 优点&#xff1a;   高并发&#xff08;最重要的优点&#xff09;   适合I/O密集型应用 缺点&#xff1a;   不适合CPU密集型应用&#xff1b;CPU密集型应用给Node带来的挑战主要是&#xff1a;由于JavaScript单线程的原因&#xff0c;如果有长时间运行的…

GO并发过高导致程序崩溃如何解决

#作者&#xff1a;曹付江 文章目录 1.并发过高导致程序崩溃2. 如何解决2.1 利用 channel 的缓存区2.2 利用第三方库 3 调整系统资源的上限3.1 ulimit3.2 虚拟内存(virtual memory) 1.并发过高导致程序崩溃 看一个非常简单的例子&#xff1a; func main() {var wg sync.WaitG…

Linux -- gdb/cgdb的认识和使用

预备知识 程序的发布⽅式有两种&#xff0c; debug 模式和 release 模式&#xff0c; Linux gcc/g 出来的⼆进制程 序&#xff0c;默认是 release 模式。 要使⽤gdb调试&#xff0c;必须在源代码⽣成⼆进制程序的时候, 加上 -g 选项&#xff0c;如果没有添加&#x…

window 显示驱动开发-Direct3D 呈现性能改进(四)

调用资源创建、映射和取消映射函数的行为更改 对于 WDDM 1.3 及更高版本驱动程序实现的这些函数&#xff0c;Direct3D 运行时为映射默认方案提供一组受限的输入值。 这些受限值仅适用于支持功能级别 11.1 及更高版本的驱动程序。 CreateResource (D3D11) 函数— 这些输入 D3…

3.python操作mysql数据库

前言&#xff1a;在现代应用程序中&#xff0c;数据库扮演者至关重要的角色。mysql是一个流行的关系型数据库管理系统&#xff0c;广泛应用于各种规模的应用中。在pytho中&#xff0c;我们可以通过连接库与mysql数据库进行交互&#xff0c;实现数据的增删改查操作。与此同时&am…

day023-网络基础与OSI七层模型

文章目录 1. 网络基础知识点1.1 网络中的单位1.2 查看实时网速&#xff1a;iftop1.3 交换机、路由器 2. 路由表2.1 查看路由表的命令2.2 路由追踪命令 3. 通用网站网络架构4. 局域网上网原理-NAT5. 虚拟机上网原理6. 虚拟机的网络模式6.1 NAT模式6.2 桥接模式6.3 仅主机模式 7.…

DeepSeek智能对话助手项目

目录&#xff1a; 1、效果图2、实现代码3、温度和TopK的作用对比 1、效果图 2、实现代码 # import gradio as gr# def reverse_text(text): # return text[::-1]# demogr.Interface(fnreverse_text,inputs"text",outputs"text")# demo.launch(share&q…

视觉中国:镜头下的中国发展图景

2025年5月下旬&#xff0c;从北国草原到江南水乡&#xff0c;从文化遗产到科技创新&#xff0c;中国大地上演着一幕幕生机勃勃的图景。河北张家口的沙狐幼崽与湿地生态和谐共生&#xff0c;湖北襄阳的茶园雕琢出诗意田园&#xff1b;北京殷商文创的活力、沈阳文物情景剧的创意&…

LabVIEW 中内存释放相关问题

在LabVIEW 编程领域&#xff0c;内存管理是一个关键且复杂的议题。我们常常关注 LabVIEW 如何将内存释放回操作系统&#xff08;OS&#xff09;&#xff0c;以及是否有方法确保在特定数据结构&#xff08;如队列、变体属性、动态数据引用 DVR 等&#xff09;销毁、删除或清空后…

基于正点原子阿波罗F429开发板的LWIP应用(4)——HTTP Server功能

说在开头 正点原子F429开发板主芯片采用的是STM32F429IGT6&#xff0c;网络PHY芯片采用的是LAN8720A(V1)和YT8512C(V2)&#xff0c;采用的是RMII连接&#xff0c;PHY_ADDR为0&#xff1b;在代码中将会对不同的芯片做出适配。 CubeMX版本&#xff1a;6.6.1&#xff1b; F4芯片组…

设计模式-结构型模式(详解)

适配器模式 将一个类的接口转换成客户端期望的另一个接口&#xff0c;解决接口不兼容问题。 适配器模式由四部分组成&#xff1a; 客户端&#xff1a;即需要使用目标接口的类 目标接口 需要适配的类&#xff0c;也就是已经存在好的功能&#xff0c;但客户端通过目标接口没办…

银河麒麟操作系统下载

产品试用申请国产操作系统、麒麟操作系统——麒麟软件官方网站 下载页面链接如上&#xff0c;申请试用即可。 申请试用填写后提交&#xff0c;界面就变成了这样&#xff0c;可以挑选适合自己的版本。 海思麒麟9006C版&#xff0c;如下&#xff1a; 本地下载&#xff1a;Kylin…

[CARLA系列--03]如何打包生成CARLA 0.9.15的非编辑版(地图的加载与卸载)

前两篇文章介绍了如何去安装可编辑版的CARLA 0.9.15&#xff0c;这个完整的工程文件实在是太大了&#xff0c;大概消耗了100个G的磁盘空间&#xff0c;当在进行一个CARLA项目的时候&#xff0c;不利于在每个开发电脑都去安装部署一套CARLA 0.9.15的源码&#xff0c;所以把自己这…

【机器学习基础】机器学习入门核心算法:朴素贝叶斯(Naive Bayes)

机器学习入门核心算法&#xff1a;朴素贝叶斯&#xff08;Naive Bayes&#xff09;&#xff09; 一、算法逻辑1.1 基本概念1.2 基本流程 二、算法原理与数学推导2.1 贝叶斯定理2.2 朴素贝叶斯分类器2.3 不同分布假设下的概率计算2.3.1 高斯朴素贝叶斯&#xff08;连续特征&…

云服务器系统盘满了,但是其他正常,是否可能是被攻击了

目录 问题背景分析解决系统盘满的问题解决结果 问题背景 今天登录我的云服务器看了眼&#xff0c;发现系统盘满了&#xff0c;但是其他正常 分析 1、首先要确认是否是被攻击&#xff1a; top / htop (安装&#xff1a;yum install htop 或 apt install htop)&#xff1a;…

双因子COX 交互 共线性 -spss

SPSS 简要界面操作步骤(针对双因子 COX 分析) 1. 数据准备 变量格式:确保数据已整理为以下格式(示例): 时间变量(如 Time_to_Recurrence)结局变量(如 Recurrence:1=复发,0=未复发)预测变量(CSPG4_HSCORE、FAM49B_Status 二分类变量)协变量(如 Lesion_Size、Pat…