【Linux】序列化与反序列化

目录

前言

什么是应用层?

再谈"协议"

 什么是序列化和反序列化

网络版计算器

整体流程实现

Sock.hpp的实现

TcpServer.hpp的实现

Protocol.hpp的实现

CalServer.cc的编写

CalClient.cc的编写

整体代码


 

前言

        本章是属于TCP/UDP四层模型中的第一层 应用层相关的内容。主要介绍了序列化与反序列化的应用,本章代码居多,主要是在代码中体现出序列化与反序列化,希望可以耐心阅读,坚持到底一定会有所收获。

什么是应用层?

  • 在网络编程中,应用层是网络协议栈中的最高层。它负责定义网络应用程序与网络通信的接口和规范,主要关注数据的格式、交互方式和应用层协议的定义。

        应用层协议是在应用程序之间进行通信时所使用的规则和约定。它定义了应用程序如何打包、发送、接收和解析数据。应用层协议可以基于不同的传输协议(如TCP、UDP)来提供不同的服务,例如文件传输、电子邮件、网页浏览、即时通信等。

        应用层协议通常在应用程序中实现,为应用程序提供了一组函数、API(例如我们前面使用的socket相关接口)或库,使得应用程序能够通过网络与其他应用程序进行通信。开发者可以使用这些功能来处理数据的编码、解码、发送和接收等操作,同时也可以根据需要定义自己的应用层协议。

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


再谈"协议"

        协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢.

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

        这里有两种方案:

约定方案一:

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

约定方案二:

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

 什么是序列化和反序列化

        序列化是将内存中的结构化数据(结构体)转换为字节序列的过程。通过序列化,可以将数据结构转换为一系列字节,使其可以被保存到磁盘或通过网络传输。序列化的结果是一个字节流或二进制数据,可以在需要时进行持久化存储或传输给其他系统。

        反序列化是将字节流或二进制数据转换回原始结构化数据(结构体)的过程。通过反序列化,可以将序列化后的数据重新还原为内存中的数据结构,以便在程序中进行处理或使用。

        序列化和反序列化通常用于在分布式系统中进行数据传输、存储和进程间通信。例如,在网络通信中,发送方将数据结构序列化为字节流,通过网络发送给远程主机,然后在远程主机上进行反序列化,以还原数据并进行处理。



网络版计算器

整体流程实现

        这个简易版的网络程序是客户端发送类似与“1+2”,“1-2”,“1/2”..,然后服务器返回对应的结果和状态码,代表结果的正确与否。

        我们编写的整体流程是:先对socket接口做封装成Sock类,提供Socket接口,Bind接口,Listen接口,Accept接口以及Connect接口.

        然后我们再对服务器做相关的封装,封装成TcpServer类,构造函数用于初始化服务器,该类对外提供BindService(用于绑定服务)接口,Excute(用来执行对应的回调函数)接口,Start(用于服务器的启动并创建线程运行对应的服务)等接口.

        然后是协议的定制,通信双方(服务端和客户端必须同时遵守这一套规定),共有两个类,分别为Request类和Response类,每个类中分别有两个接口 Serialization(序列化)与Deserialization(反序列化),用于格式的转化。

        最后再分别编写服务端和客户端即可,


Sock.hpp的实现

        这一个文件主要是封装socket的相关接口,到时候我们使用相关socket接口时,只需要调用Sock类中的相关接口即可.

        第一个是Socket函数,封装了socket,并得到一个套接字,将该套接字返回,代码如下:

    int Socket(){// 1.创建套接字int listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create sock success,  listensock: %d", listensock);return listensock;}

        第二个是Bind接口,同样地我们首先需要创建一个sockaddr_in结构体,然后填入相关的数据,这个前几章已经说过好多次了,便不再详细说明了,然后调用bind绑定。

    int Bind(int sock, uint16_t port, string ip = "0.0.0.0"){// 2.bindstruct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;//使用的协议簇为IPv4local.sin_port = htons(port);//填入端口号local.sin_addr.s_addr = INADDR_ANY;//填入ip地址if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind error", errno, strerror(errno));exit(3);}}

        第三个是Listen接口,用于对客户端请求的监听,内部只需要调用listen接口并判断即可.

    void Listen(int sock){if (listen(sock, gbacklog) < 0){logMessage(FATAL, "listen error", errno, strerror(errno));exit(3);}logMessage(NORMAL, "init server success");}

        第四个是Accept接口,和上一章所说的一样,先建立一个sockaddr_in结构体,用于接收保存客户端的相关信息(ip和端口等等),同时会得到一个新的套接字,返回这个套接字即可。

    int Accept(int listensock, string *ip, uint16_t *port){struct sockaddr_in src;socklen_t len = sizeof src;int servicesock = accept(listensock, (struct sockaddr *)&src, &len);if (servicesock < 0){logMessage(ERROR, "accept error", errno, strerror(errno));return -1;}if (port)*port = ntohs(src.sin_port);if (ip)*ip = inet_ntoa(src.sin_addr);return servicesock;}

        最后一个是Connect接口,和之前的用法一样,先创建一个sockaddr_in结构体,然后填入客户端本身的相关信息,最后调用connect函数接口.

    bool Conncect(int sock,string server_ip, uint16_t server_port){struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr=inet_addr(server_ip.c_str());if(connect(sock,(struct sockaddr*)&server,sizeof server) == 0) return true;else {perror("connect"); return false; }}

TcpServer.hpp的实现

        这个类中首先要有这3个成员:listensock_用于监听的套接字,Sock sock,上面的Sock类的一个对象,用于接口的调用,vector<func_t> func_,用于存储相关的服务. func_t是一个函数指针.

定义如下:

   using func_t = function<void(int)>;

        首先是构造函数,需要对服务器进行初始化,包括创建套接字,绑定与监听,如下:

        TcpServer(const uint16_t &port, const string &ip = "0.0.0.0"){listensock_ = sock_.Socket();sock_.Bind(listensock_, port, ip);sock_.Listen(listensock_);}

        然后是BindService,用于将绑定某一个服务,将其加入到func_即可.

        void BindService(func_t func){func_.push_back(func);}

        接下来是Excute函数,用于执行func_中的方法:

        void Excute(int sock){for(auto & f : func_){f(sock);}}

        然后是Start函数,用于服务器的启动,调用Accept等待客户端的连接,当有客户端连接后,创建一个新的线程,并调用对应的回调函数,于此同时,我们新建一个ThreadData类,用于存储该线程的相关信息,包括该线程的套接字和调用该线程的this指针.如下:

        void Start(){for (;;){string clientip;uint16_t clientport;int sock = sock_.Accept(listensock_, &clientip, &clientport);if (sock == -1)continue;logMessage(NORMAL, "Create new link success, sock: %d", sock);pthread_t tid;ThreadData* td= new ThreadData(sock,this);pthread_create(&tid, nullptr, ThreadRoutine, td);}}

ThreadData类:

    class ThreadData{public:ThreadData(int sock,TcpServer* server):sock_(sock),server_(server){}~ThreadData(){}public:int sock_;TcpServer* server_;};

回调函数ThreadRoutine

        static void *ThreadRoutine(void *args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->server_->Excute(td->sock_);close(td->sock_);return nullptr;}

Protocol.hpp的实现

        这个类主要是对发送方和应答方进行一个协议的定制,方便发送和解析数据。

所以一共两个类,Request类和Response类.

  • 先来看Request类,即请求方。该类主要是对请求数据的序列化和反序列化.
  • 共有3个类成员:x_,y_,op_,分别代表第一个操作、第二个操作数、和符号.

        第一个接口Serialization序列化,它每次需要把结构化数据转化成字符串形式,我们规定形式为

“x_ op_ y_”,所以代码如下:

        string Serialization(){// 1.自主实现 "x_ op_ y_\r\n"string str;str = to_string(x_);str += SPACE;str += op_;str += SPACE;str += to_string(y_);return str;}

        第二个接口是请求方 Deserialization反序列化的实现,需要将字符串数据转化成结构化的数据,首先我们要解析字符串,分别提取出x,y和op,然后赋值给这个类成员(x_,y_,op_),便成功的反序列化了.

        bool Deserialization(const string &str){//提取x的第一个字符size_t left = str.find(SPACE);if (left == string::npos)return false;//提取y的第一个字符size_t right = str.rfind(SPACE);if (right == string ::npos)return false;//得到整数xx_ = atoi(str.substr(0, left).c_str());//得到整数yy_ = atoi(str.substr(right + SPACE_LEN).c_str());if (left + SPACE_LEN > str.size())return false;elseop_ = str[left + SPACE_LEN];return true;}
  •  Response类

  • 该类主要是对处理结果的序列化和反序列化,类成员有result_和code_,分别代表结果和结果状态码.

它的序列化形式我们规定为“result_ code_”,所以代码如下:

        // code_ resultstring Serialization(){string s;s = to_string(code_);s += SPACE;s += to_string(result_);return s;}

反序列化逻辑和Request类一样。分别提取,然后赋值给类成员:

        bool Deserialization(const string& s){size_t pos = s.find(SPACE);if(pos == string::npos){return false;}code_ = atoi(s.substr(0,pos).c_str());result_ = atoi(s.substr(pos+SPACE_LEN).c_str());return true;}

于此同时,还封装了两个函数,Recv和Send.

实现分别如下:

    string Recv(int sock){//tcp是面向字节流的char inbuffer[1024];ssize_t s = recv(sock, inbuffer, sizeof inbuffer, 0);if (s > 0){inbuffer[s] = '\0';return inbuffer;}else if(s == 0){cout << "client quit" << endl;}else{cout << "recv error" << endl;}return "";}
    void Send(int sock, const string str){send(sock, str.c_str(), str.size(), 0);}

到这里我们基本工作就完成了一大半,接下来是服务端和客户端的编写

CalServer.cc的编写

        这是服务端的代码,首先我们都是要忽略SIGPIPE信号的,这是为了避免进程在写入已关闭的套接字时触发SIGPIPE信号而终止。

        例如,当一个客户端与服务器建立连接后,但在向客户端发送响应前,客户端已经关闭了连接。如果服务器端没有忽略SIGPIPE信号,那么在尝试向已关闭的连接写入数据时,服务器进程将会被终止,这显然是不希望看到的行为。

    signal(SIGPIPE, SIG_IGN);

        然后我们需要绑定一个服务,我们要进行计算,所以假设服务 方法是calculator,该函数首先要从调用Recv读取到请求,如果读取结果不为空的话,我们将读取到的数据进行反序列化成结构化的数据(Request类),然后再将这个结构化的数据传送到calculatorHelp()函数进行计算,再次得到一个结构化的数据(Response类),最后再对这个数据进行序列化成字符串形式,再Send回请求的客户端中.

static Response calculatorHelp(Request &req)
{Response resp(0, 0);switch (req.op_){case '+':resp.result_ = req.x_ + req.y_;break;case '-':resp.result_ = req.x_ - req.y_;break;case '*':resp.result_ = req.x_ * req.y_;break;case '/':if (req.y_ == 0)resp.code_ = 1;elseresp.result_ = req.x_ / req.y_;break;case '%':if (req.y_ == 0)resp.code_ = 2;resp.result_ = req.x_ % req.y_;break;default:resp.code_ = 3;break;}return resp;
}
void calculator(int sock)
{while (true){string str = Recv(sock); // 在这里我们读到了一个请求if (!str.empty()){Request req;req.Deserialization(str); // 反序列化Response resp = calculatorHelp(req);string respString = resp.Serialization(); // 对计算结果进行序列化Send(sock, respString);}else{break;}}
}
int main()
{//....server->BindService(calculator);//....
}

一切完成后,我们在Start启动服务器即可.

    server->Start();

CalClient.cc的编写

        客户端编写就比较简单了,先创建套接字,然后获取到用户输入的ip和port,然后进行Connect,连接完成后,提示用户输入数据,输入完成后,将数据序列化,然后发送给服务器,然后接收服务器返回的数据,再将其反序列化即可。

int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);Sock sock;int sockfd = sock.Socket();cout << sockfd << endl;if (!sock.Conncect(sockfd, serverip, serverport)){cerr << "connect error" << endl;exit(2);}while (true){Request req;cout << "Please Enter x# ";cin >> req.x_;cout << "Please Enter y#" ;cin >> req.y_;cout << "Please Enter operator# ";;cin >> req.op_; ;string s = req.Serialization();Send(sockfd, s);string r = Recv(sockfd);Response resp;resp.Deserialization(r);cout << "code: " << resp.code_ << endl;cout << "result: " << resp.result_ << endl;sleep(1);}return 0;
}

代码效果图:

 


 

整体代码

如果你不想仔细看,直接拷贝下面的代码也可以运行,然后可以自己研究:

  • Sock.hpp
#pragma once
#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <memory>
#include <pthread.h>
#include <signal.h>
#include <cstring>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"using namespace std;
class Sock
{
public:const static int gbacklog = 20;Sock(){}int Socket(){// 1.创建套接字int listensock = socket(AF_INET, SOCK_STREAM, 0);if (listensock < 0){logMessage(FATAL, "%d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create sock success,  listensock: %d", listensock);return listensock;}int Bind(int sock, uint16_t port, string ip = "0.0.0.0"){// 2.bindstruct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;//使用的协议簇为IPv4local.sin_port = htons(port);//填入端口号//local.sin_addr.s_addr = ip.empty() ? INADDR_ANY : inet_addr(ip.c_str());local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind error", errno, strerror(errno));exit(3);}}void Listen(int sock){// 3.因为TCP是面向连接的,意味着当我们正式通信的时候,需要先建立连接if (listen(sock, gbacklog) < 0){logMessage(FATAL, "listen error", errno, strerror(errno));exit(3);}logMessage(NORMAL, "init server success");}// const string& 输入型参数// string* 输出型参数// string& 输入输出型参数int Accept(int listensock, string *ip, uint16_t *port){struct sockaddr_in src;socklen_t len = sizeof src;int servicesock = accept(listensock, (struct sockaddr *)&src, &len);if (servicesock < 0){logMessage(ERROR, "accept error", errno, strerror(errno));return -1;}if (port)*port = ntohs(src.sin_port);if (ip)*ip = inet_ntoa(src.sin_addr);return servicesock;}bool Conncect(int sock,string server_ip, uint16_t server_port){struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr=inet_addr(server_ip.c_str());// cout << server.sin_port << " " << server.sin_addr.s_addr << endl;if(connect(sock,(struct sockaddr*)&server,sizeof server) == 0) return true;else {perror("connect"); return false; }}~Sock(){}};
  • TcpServer.hpp
​
#pragma once
#include "Sock.hpp"
#include <vector>
#include <functional>
#include <pthread.h>
namespace ns_TcpServer
{class TcpServer;using func_t = function<void(int)>;class ThreadData{public:ThreadData(int sock,TcpServer* server):sock_(sock),server_(server){}~ThreadData(){}public:int sock_;TcpServer* server_;};class TcpServer{private:static void *ThreadRoutine(void *args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->server_->Excute(td->sock_);close(td->sock_);return nullptr;}public:TcpServer(const uint16_t &port, const string &ip = "0.0.0.0"){listensock_ = sock_.Socket();sock_.Bind(listensock_, port, ip);sock_.Listen(listensock_);}void BindService(func_t func){func_.push_back(func);}void Excute(int sock){for(auto & f : func_){f(sock);}}void Start(){for (;;){string clientip;uint16_t clientport;int sock = sock_.Accept(listensock_, &clientip, &clientport);if (sock == -1)continue;logMessage(NORMAL, "Create new link success, sock: %d", sock);pthread_t tid;ThreadData* td= new ThreadData(sock,this);pthread_create(&tid, nullptr, ThreadRoutine, td);}}~TcpServer(){if (listensock_ >= 0)close(listensock_);}private:int listensock_;Sock sock_;vector<func_t> func_;};
}​
  • Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>using namespace std;namespace ns_protocol
{
#define SPACE " "
#define SPACE_LEN strlen(SPACE)class Request{public:string Serialization(){// 1.自主实现 "x_ op_ y_\r\n"string str;str = to_string(x_);str += SPACE;str += op_;str += SPACE;str += to_string(y_);return str;}bool Deserialization(const string &str){size_t left = str.find(SPACE);if (left == string::npos)return false;size_t right = str.rfind(SPACE);if (right == string ::npos)return false;x_ = atoi(str.substr(0, left).c_str());y_ = atoi(str.substr(right + SPACE_LEN).c_str());if (left + SPACE_LEN > str.size())return false;elseop_ = str[left + SPACE_LEN];return true;}public:Request(int x, int y, char op) : x_(x), y_(y), op_(op){}Request(){}~Request() {}public:int x_;int y_;char op_;};class Response{public:// code_ resultstring Serialization(){string s;s = to_string(code_);s += SPACE;s += to_string(result_);return s;}bool Deserialization(const string& s){size_t pos = s.find(SPACE);if(pos == string::npos){return false;}code_ = atoi(s.substr(0,pos).c_str());result_ = atoi(s.substr(pos+SPACE_LEN).c_str());return true;}public:Response(int res, int code) : result_(result_), code_(code){}Response(){}~Response() {}public:int result_; // 计算结果int code_;   // 计算结果状态码};string Recv(int sock){//tcp是面向字节流的char inbuffer[1024];ssize_t s = recv(sock, inbuffer, sizeof inbuffer, 0);if (s > 0){inbuffer[s] = '\0';return inbuffer;}else if(s == 0){cout << "client quit" << endl;}else{cout << "recv error" << endl;}return "";}void Send(int sock, const string str){send(sock, str.c_str(), str.size(), 0);}
}
  • CalServer.cc
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>
#include <signal.h>using namespace ns_TcpServer;
using namespace ns_protocol;
using namespace std;static void usage(string proc)
{cout << "\n Usage: " << proc << "port\n"<< endl;
}
static Response calculatorHelp(Request &req)
{Response resp(0, 0);switch (req.op_){case '+':resp.result_ = req.x_ + req.y_;break;case '-':resp.result_ = req.x_ - req.y_;break;case '*':resp.result_ = req.x_ * req.y_;break;case '/':if (req.y_ == 0)resp.code_ = 1;elseresp.result_ = req.x_ / req.y_;break;case '%':if (req.y_ == 0)resp.code_ = 2;resp.result_ = req.x_ % req.y_;break;default:resp.code_ = 3;break;}return resp;
}
void calculator(int sock)
{while (true){string str = Recv(sock); // 在这里我们读到了一个请求if (!str.empty()){Request req;req.Deserialization(str); // 反序列化Response resp = calculatorHelp(req);string respString = resp.Serialization(); // 对计算结果进行序列化Send(sock, respString);}else{break;}}
}//./calServer port
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(1);}//一般经验:server在编写的时候,要有较为严谨的判断逻辑//一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法访问问题signal(SIGPIPE, SIG_IGN);unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));server->BindService(calculator);server->Start();
}
  • CalClient.cc
#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"
using namespace std;
using namespace ns_protocol;static void usage(string proc)
{cout << "\n Usage: " << proc << " serverIp serverPort\n"<< endl;
}
//./clinet server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);Sock sock;int sockfd = sock.Socket();cout << sockfd << endl;if (!sock.Conncect(sockfd, serverip, serverport)){cerr << "connect error" << endl;exit(2);}while (true){Request req;cout << "Please Enter x# ";cin >> req.x_;cout << "Please Enter y#" ;cin >> req.y_;cout << "Please Enter operator# ";;cin >> req.op_; string s = req.Serialization();Send(sockfd, s);string r = Recv(sockfd);Response resp;resp.Deserialization(r);cout << "code: " << resp.code_ << endl;cout << "result: " << resp.result_ << endl;sleep(1);}return 0;
}

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

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

相关文章

VSCode连接服务器

Pycharm连接服务器参考我的另一篇文章Pycharm远程连接服务器_pycharm进入服务器虚拟环境终端_Jumbo星的博客-CSDN博客 本质上Pycharm和VSCode都只是IDE&#xff0c;没有什么好坏之分。但是因为Pycharm连接服务器&#xff08;准确来说是部署&#xff09;需要买professional。而…

Shell 脚本入门

目录 一、Shell是什么 1.1 我们为什么要学习Shell和使用Shell&#xff1f; 1.2 Shell的分类有哪些&#xff1f; 二、Shell脚本入门知识 2.1 Shell文件命名规范 2.2 Shell解析器 2.3 用Shell 编写hello World 三、Shell的四种变量类型 3.1 系统预定义变量 3.2 自定义变…

JavaWeb 速通Ajax

目录 一、Ajax快速入门 1.基本介绍 : 2.使用原理 : 二、Ajax经典入门案例 1.需求 : 2.前端页面实现 : 3. 处理HTTP请求的servlet实现 4.引入jar包及druid配置文件、工具类 : 5.Domain层实现 : 6.DAO层实现 : 7.Service层实现 : 8.运行测试 : 三、JQuery操作Ajax 1 …

kvm虚拟机开启VNC功能

停止kvm虚拟机 virsh shutdown 虚拟机名称 在kvm虚拟机配置文件里面添加如下内容 <graphics typevnc port-1 autoportyes listen0.0.0.0 keymapen-us passwd123456> 启动kvm虚拟机 virsh start 虚拟机名称 得到虚拟机进程id ps -ef|grep 虚拟机名称 得到虚拟机vnc…

kubernetes deploy standalone mysql demo

kubernetes 集群内部署 单节点 mysql ansible all -m shell -a "mkdir -p /mnt/mysql/data"cat mysql-pv-pvc.yaml apiVersion: v1 kind: PersistentVolume metadata:name: mysql-pv-volumelabels:type: local spec:storageClassName: manualcapacity:storage: 5Gi…

使用Rust开发命令行工具

生成二进制文件&#xff0c;将其扔到环境变量的path下即可~ 用rust打造实时天气命令行工具[1] 找到合适的API 使用该api[2] 如请求 api.openweathermap.org/data/2.5/weather?qBeijing&appidyour_key: { "coord": { "lon": 116.3972, "lat&quo…

【爬虫小知识】如何利用爬虫爬网页——python爬虫

前言 网络时代的到来&#xff0c;给我们提供了海量的信息资源&#xff0c;但是&#xff0c;想要获取这些信息&#xff0c;手动一个一个网页进行查找&#xff0c;无疑是一项繁琐且效率低下的工作。这时&#xff0c;爬虫技术的出现&#xff0c;为我们提供了一种高效的方式去获取…

无涯教程-分类算法 - 随机森林

随机森林是一种监督学习算法&#xff0c;可用于分类和回归&#xff0c;但是&#xff0c;它主要用于分类问题&#xff0c;众所周知&#xff0c;森林由树木组成&#xff0c;更多树木意味着更坚固的森林。同样&#xff0c;随机森林算法在数据样本上创建决策树&#xff0c;然后从每…

利用PDF.js在微信小程序里预览PDF文件

在微信小程序可以通过wx.downloadFile 和 wx.openDocument 两个api下载并打开pdf文件。这种方式主要有不少的缺点&#xff1a; 1、需要下载才可以查看&#xff0c;且每次打开都需要下载生成一个临时文件&#xff0c;如果PDF文件比较多的话&#xff0c;临时文件会越来越多&…

【问题】jdk20执行虚拟线程报错解决方案

java: ofPlatform() 是预览 API&#xff0c;默认情况下处于禁用状态。 &#xff08;请使用 --enable-preview 以启用预览 API&#xff09; 在idea增加配置 编辑配置 增加jvm参数 然后去执行 public static void main(String[] args) {int 次数 100000;System.out.println(&q…

centos升级python3.10,中间遇到问题,终于解决了

升级为python3.10 yum updateyum install openssl-devel bzip2-devel libffi-develyum groupinstall "Development Tools"wget https://www.python.org/ftp/python/3.10.2/Python-3.10.2.tgztar -xzf Python-3.10.2.tgzcd Python-3.10.2./configure --enable-optimi…

硬件知识积累 USB 接口 type - A type - B type - C 的介绍与功能说明 (简单介绍)

1. USB 的介绍 1.1 USB 的定义 USB : 通用串行总线(英语: Universal Serial Bus&#xff0c;缩写:USB)是一种串口总线标准&#xff0c;也是一种输入输出接口的技术规范&#xff0c;被广泛地应用于个人电脑和移动设备等信息通讯产品&#xff0c;并扩展至摄影器材、数字电视&a…

电商项目part05 分布式ID服务实战

背景 日常开发中&#xff0c;需要对系统中的各种数据使用 ID 唯一表示&#xff0c;比如用户 ID 对应且仅对应一个人&#xff0c;商品 ID 对应且仅对应一件商品&#xff0c;订单 ID 对应且仅对应 一个订单。现实生活中也有各种 ID&#xff0c;比如身份证 ID 对应且仅对应一个人…

C#,《小白学程序》第七课:列表(List)应用之一“编制高铁车次信息表”

1 文本格式 /// <summary> /// 车站信息类 class /// </summary> public class Station { /// <summary> /// 编号 /// </summary> public int Id { get; set; } 0; /// <summary> /// 车站名 /// </summary>…

【无法联网】电脑wifi列表为空的解决方案

打开电脑, 发现wifi列表为空, 点击设置显示未连接 首先检查是不是网卡驱动有问题, cmd, devmgmt.msc 找到网络适配器, 看看网卡前面是否有感叹号, 如果没有则说明网卡没问题, 有问题则重装驱动 看看网络协议是否设置正确 找到"控制面板\所有控制面板项\网络和共享中心&…

AP5192 DC-DC降压恒流LED汽车灯 LED长条灯 汽车雾灯驱动IC

AP5192是一款PWM工作模式,高效率、外围简单、 内置功率MOS管&#xff0c;适用于4.5-100V输入的高精度 降压LED恒流驱动芯片。最大电流1.5A。 AP5192可实现线性调光和PWM调光&#xff0c;线性调光 脚有效电压范围0.55-2.6V. AP5192 工作频率可以通过RT 外部电阻编程 来设定&…

力扣:73. 矩阵置零(Python3)

题目&#xff1a; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚…

【LeetCode-中等题】105. 从前序与中序遍历序列构造二叉树

文章目录 题目方法一&#xff1a;递归 题目 方法一&#xff1a;递归 preorder [3,9,20,15,7] inorder [9,3,15,20,7] 首先根据 preorder 找到根节点是 3然后根据根节点将 inorder 分成左子树和右子树 左子树 inorder [9]右子树 inorder [15,20,7]这时候3是根节点 3的左子树…

基于Java+SpringBoot+Mybaties-plus+Vue+ElementUI 高校汉服租赁网站的 设计与实现

一.项目介绍 高校汉服租赁网站分为普通用户以及管理员两类 普通用户&#xff1a; 注册、登录系统、查看汉服首页发帖公告信息、 交流论坛&#xff08;发帖、查看帖子、评论&#xff09;、 公告咨询&#xff08;查看公告以及评论&#xff09;、 汉服信息&#xff08;查…

k8s 查看加入主节点命令 k8s重新查看加入节点命令 k8s输入删除,重新查看加入命令 kuberadm查看加入节点命令

1. 使用kuberadm 安装成功后&#xff0c;clear清除了屏幕数据&#xff0c;加入命令无法查看&#xff0c;使用如下&#xff0c;重新查看node如何加入主节点命令&#xff1a; kubeadm token create --print-join-command --ttl 0 2.画圈的全部是&#xff0c;都复制&#xff0c;在…