【計算機網絡】應用層自定義協議

自定義協議

  • 一、為什么需要自定義協議?
  • 二、網絡版計算器
    • 1. 基本要求
    • 2. 序列化和反序列化
    • 3. 代碼實現
      • (1)封裝 socket
      • (2)定制協議和序列化反序列化
      • (3)客戶端
      • (4)計算器服務端
      • (5)TCP服務端
      • (6)啟動服務器
    • 4. 使用 JSON 進行序列化和反序列化
      • (1)安裝 JSON 庫
      • (2)測試 JSON
      • (3)在網絡計算器中使用 JSON

一、為什么需要自定義協議?

我們上個知識點編寫的TCP中,TCP是面向字節流的,我們怎么保證讀取上來的數據是一個完整的報文呢?其實我們寫的代碼中不能保證這個問題,所以代碼是有BUG的。TCP 叫做傳輸控制協議,也就是什么時候發送給對方,發多少,出錯了怎么辦,完全是由發送方的 TCP 協議來定!當我們使用 write() 函數向 sockfd 中寫入數據時,數據不一定已經發給對方了,它的作用其實就是用戶到內核的拷貝!這跟我們以前學的向文件中寫入是一樣的,我們將數據通過 fd 寫入到內核的緩沖區,通過操作系統向磁盤中刷新緩沖區的內容。所以真正決定網路收發的協議是由 TCP 決定的!

而對于接收緩沖區來說,我們使用 read() 讀取上來的數據就完全不確定了。所以我們在應用層就需要把協議定好,把協議定好才能更好的進行讀上來的數據的分析!

所以回到最開始的問題,我們在進行讀取的時候,怎么保證讀取上來的數據是一個完整的報文呢?對于發送方,是將數據拷貝到它的 TCP 的發送緩沖區了,我們想怎么發完全不由應用層決定,是由 TCP 決定的,所以對方在它的接收緩沖區讀上來的數據有可能是我們發送的一部分!所以對方在讀的時候,怎么保證讀到的是完整的呢?這就需要協議來進行定制了!

在這里插入圖片描述

所以我們可以規定好通信雙方只能使用固定大小的報文,即我們自己使用自定義協議。

二、網絡版計算器

1. 基本要求

例如,我們需要實現一個服務器版的加法器。我們需要客戶端把要計算的兩個加數發過去,然后由服務器進行計算,最后再把結果返回給客戶端。

在應用層定協議,我們通常需要一個比較關鍵的字段。首先,協議本身就是一種“約定”,假設我們以實現網絡版計算器為例,那么我們需要定義的第一個協議就是 request,代表需要相加的兩個數和一個操作符,如下:

				struct request{int x;int y;char op;};

另外還需要定義另一個協議為 response,代表運算結果和正確性,如下:

				struct response{int result;int code;};

所以每一個結構體的每一個字段,每一個字段里的每一種值,我們都是要讓客戶端和服務器雙方約定好的,約定好之后,我們使用結構化的方式,把約定表達出來,這就叫做我們定義出來的協議。

2. 序列化和反序列化

當我們向對方發信息時, 不僅僅只包含我們所發的信息,還有對應的頭像,昵稱和時間等等,實際上這些都是一個個的字符串,所以對方會收到四個字符串,但是肯定不能一個個發,是要把它們看作一個整體發給對方;而對方在收到這個整體的字符串后,就要將這個整體的字符串反向的轉化成四個字符串,解析成信息內容、頭像、昵稱和時間。

那么怎么將這些信息看作一個整體呢?我們可以把需要發送的一個信息看作是一個結構體,其中這個結構體中有四個字段,分別代表上面的四個字符串;然后我們再把這個結構化的數據轉化成為一個字符串,緊接著將這個字符串整體通過網絡發送給對方主機,當對方主機收到這個字符串后,需要將這個字符串解析成為相同類型的結構化數據!在這個消息轉化的過程,也是規定出來客戶端和服務器雙方約定出來的一種通用型的結構體,這就叫做雙方定義出來的聊天協議。而在網絡通信的時候,整個結構化的數據,把它多個字符串轉化成一個字符串整體,這個過程我們稱為序列化!而對方把一個字符串整體打散稱為多個字符串這個過程稱為反序列化

在這里插入圖片描述

而以上的過程我們可以看作兩層,一層是協議的定制,另一層是序列化和反序列化,如下圖:

在這里插入圖片描述

那么為什么需要進行序列和反序列化呢?主要是為了方便網絡進行收發!

所以根據我們自定義的協議和序列化反序列化,我們的網絡版計算機的簡略流程如下:

在這里插入圖片描述

下面我們根據上圖的流程圖簡易實現一個網絡版的計算器。

3. 代碼實現

(1)封裝 socket

每次提供網絡通信都要重新編寫 socket 套接字的代碼,所以我們現在這里對 socket 進行一下簡單的封裝,代碼如下:

				#pragma once#include <iostream>#include <string>#include <cstring>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include "log.hpp"enum{SocketErr = 2,BindErr, ListenErr,};const int backlog = 10;class Sock {public:Sock(){}~Sock(){}public:void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){lg(Fatal, "socket error, %s: %d", strerror(errno), errno);exit(SocketErr);}}void Bind(uint16_t port){sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_addr.s_addr = INADDR_ANY;local.sin_family = AF_INET;local.sin_port = htons(port);if(bind(_sockfd, (const sockaddr*)&local, sizeof(local)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if(listen(_sockfd, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string* client_ip, uint16_t* client_port){sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(_sockfd, (sockaddr*)&peer, &len);if(newfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char buffer[64];inet_ntop(AF_INET, &peer.sin_addr, buffer, sizeof(buffer));*client_ip = buffer;*client_port = ntohs(peer.sin_port);return newfd;}void Close(){close(_sockfd);}bool Connect(std::string serverip, uint16_t serverport){sockaddr_in peer;memset(&peer, 0, sizeof(peer));inet_pton(AF_INET, serverip.c_str(), &(peer.sin_addr));peer.sin_family = AF_INET;peer.sin_port = htons(serverport);int n = connect(_sockfd, (const sockaddr*)&peer, sizeof(peer));if(n < 0){lg(Fatal, "connect error, %s: %d", strerror(errno), errno);return false;}return true;}int GetFd(){return _sockfd;}private:int _sockfd;};

(2)定制協議和序列化反序列化

在進行定制協議的時候,為了保證對方接受時是一個完整的報文,也就是當對方進行讀取時,對方怎么知道是一個報文多大多長呢?所以我們需要使用分隔符將報文和報文之間分隔開來,比如可以使用 \n,也就是使用 \n 對報文之間進行分隔。 但是我們在實現的時候,在報文前再加上一個字段,就是代表有效報文的長度,長度和報文之間也是使用 \n 進行分隔。那么在進行讀取的時候,在遇到第一個 \n 之前,就是該報文的長度,然后根據長度去讀取報文,就能保證讀取到一個完整的報文,當遇到第二個 \n 就代表本次讀取完畢,進行下一次讀取。

Request

				const std::string blank_space_sep = " ";const std::string protocol_sep = "\n";// 定制協議class Request{public:Request(int x, int y, char op): _x(x), _y(y), _op(op){}Request(){}public:// 序列化bool Serialize(std::string *out){// 構建報文的有效載荷// struct => string// "len"\n"x op y"\nstd::string s = std::to_string(_x);s += blank_space_sep;s += _op;s += blank_space_sep;s += std::to_string(_y);*out = s;return true;}// 反序列化bool Deserialize(const std::string &in) // "x op y"{size_t left = in.find(blank_space_sep);if(left == std::string::npos) return false;std::string part_x = in.substr(0, left);size_t right = in.rfind(blank_space_sep);if(right == std::string::npos) return false;std::string part_y = in.substr(right + 1);if(left + 1 != right - 1) return false;_op = in[left + 1];_x = std::stoi(part_x);_y = std::stoi(part_y);return true;}void DebugPrint(){std::cout << "新請求構建完成: " << _x << _op << _y << "=?" << std::endl; }public:int _x;int _y;char _op;};

Response

				class Response{public:Response(int result, int code): _result(result), _code(code){}Response(){}public:// 序列化bool Serialize(std::string *out){// "len"\n"result code"\nstd::string s = std::to_string(_result);s += blank_space_sep;s += std::to_string(_code);*out = s;return true;}// 反序列化bool Deserialize(const std::string &in)     // "result code"{size_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part_left = in.substr(0, pos);std::string part_right = in.substr(pos + 1);_result = std::stoi(part_left);_code = std::stoi(part_right);return true;}void DebugPrint(){std::cout << "結果響應完成, result: " << _result << ", code: "<< _code << std::endl; }public:int _result;int _code; // 表示結果的準確性};

下面對封裝報頭和提取報文也進行簡單封裝:

				// 封裝報頭   "x op y" =>  "len"\n"x op y"\nstd::string Encode(std::string &content){std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;}// 提取報文   "len"\n"x op y"\n => "x op y"bool Decode(std::string& package, std::string* content){size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);size_t len = stoi(len_str);// package = len_str + content_str + 2(\n)// size_t total_len = len_str.size() + len + 2*protocol_sep.size();size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos + 1, len);// 如果已經得到一個完整的報文,需要移除這個報文package.erase(0, total_len);return true;}

(3)客戶端

客戶端首先創建需求,然后將需求序列化,并添加報頭后通過網絡進行發送,當服務端把計算結果返回響應時,客戶端進行讀取,將數據提取報文,并反序列化得到結果。

				#include <iostream>#include <string>#include <ctime>#include <unistd.h>#include "Socket.hpp"#include "Protocol.hpp"using namespace std;void Usage(const string& str){cout << "\nUsage: " << str << " serverip serverport\n\n" << endl;}int main(int argc, char* argv[]){if(argc != 3){Usage(argv[0]);exit(0);}uint16_t server_port = stoi(argv[2]);string server_ip = argv[1];Sock _sock;_sock.Socket();bool ret = _sock.Connect(server_ip, server_port); if(!ret){cerr << "client connect error" << endl;return 1;}srand(time(nullptr));int cnt = 1;string operas = "+-*/%=^&";string inbuffer_stream;while(cnt <= 10){cout << "====================第" << cnt << "次測試......" << endl;int x = rand() % 100 + 1;usleep(1000);int y = rand() % 100;usleep(1000);char op = operas[rand() % operas.size()];Request req(x, y, op);req.DebugPrint();string package;req.Serialize(&package);package = Encode(package);std::cout << "最新請求: \n" << package;write(_sock.GetFd(), package.c_str(), package.size());char buffer[128];size_t n = read(_sock.GetFd(), buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;inbuffer_stream += buffer;  // "len"\n"result code"\nstd::cout << inbuffer_stream << std::endl;string content;bool ret = Decode(inbuffer_stream, &content);   // "result code"if(!ret){cerr << "Decode err" << endl;return 2;}Response resp;ret = resp.Deserialize(content);if(!ret){cerr << "Deserialize err" << endl;return 3;}resp.DebugPrint();}cnt++;cout << "=================================" << endl;sleep(1);}_sock.Close();return 0;}

(4)計算器服務端

計算器服務端的 Calculator 方法對 package 進行提取報文,獲取到需要計算的數據,然后進行反序列化進行計算后,再根據 Response 進行序列化,最后添加報頭后返回。

				#pragma once#include <string>#include <iostream>#include "Protocol.hpp"enum{DIV_ERR = 1,MOD_ERR = 2,OP_ERR = 3};class ServerCal{public:ServerCal(){}Response CalculatorHelper(const 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 = MOD_ERR;elseresp._result = req._x % req._y;}break;case '/':{if (req._y == 0)resp._code = DIV_ERR;elseresp._result = req._x / req._y;}break;default:resp._code = OP_ERR;break;}return resp;}// "len"\n"10 + 20"\nstd::string Calculator(std::string &package){std::string content;bool ret = Decode(package, &content); // content = "10 + 20"if (!ret)return "";Request req;ret = req.Deserialize(content); // x = 10, y = 20, op = '+'if (!ret)return "";content = "";Response resp = CalculatorHelper(req); // result = 30, code = 0resp.Serialize(&content);              // content = "30 0"content = Encode(content);             // content = "len"\n"30 0\n"return content;}~ServerCal(){}};

(5)TCP服務端

TCP服務端獲取到新連接后,根據返回的sockfd就可以進行網絡通信,也就是獲取到客戶端的連接請求,緊接著我們創建子進程為其提供服務,首先進行數據讀取,將讀取到的數據每次添加到 inbuffer_stream 中,每次獲取到數據都進行調用計算器服務端的 Calculator 方法,嘗試對獲取到的數據進行處理,如果處理成功,會在 Decode 方法中將已經提取的報文移除,所以不影響下次讀取。當成功調用 Calculator 方法,就將計算結果發送回去。

				#pragma once#include <signal.h>#include <functional>#include "Socket.hpp"#include "log.hpp"using func_t = std::function<std::string(std::string &)>;class TcpServer{public:TcpServer(uint16_t port, func_t callback): _port(port), _callback(callback){}~TcpServer(){}bool InitServer(){_listen_sock.Socket();_listen_sock.Bind(_port);_listen_sock.Listen();lg(Info, "init server done");return true;}void Start(){signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);while (true){// 獲取連接std::string client_ip;uint16_t client_port;int sockfd = _listen_sock.Accept(&client_ip, &client_port);if (sockfd < 0)continue;lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, client_ip.c_str(), client_port);// 提供服務if (fork() == 0){_listen_sock.Close();std::string inbuffer_stream;// 數據計算while (true){char buffer[1280];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;inbuffer_stream += buffer;lg(Debug, "debug: \n%s", inbuffer_stream.c_str());while (true){// 如果解析失敗,會返回空串std::string info = _callback(inbuffer_stream);if (info.empty())break;write(sockfd, info.c_str(), info.size());}}else if (n == 0)break;elsebreak;}exit(0);}close(sockfd);}}private:uint16_t _port;Sock _listen_sock;func_t _callback;};

(6)啟動服務器

				#include "TcpServer.hpp"#include "Protocol.hpp"#include "ServerCal.hpp"using namespace std;void Usage(const string& str){cout << "\nUsage: " << str << " port\n\n" << endl;}int main(int argc, char* argv[]){if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);ServerCal cal;TcpServer* tsvp = new TcpServer(port, bind(&ServerCal::Calculator, &cal, std::placeholders::_1));tsvp->InitServer();tsvp->Start();return 0;}

4. 使用 JSON 進行序列化和反序列化

JSON 其實是一種幫我們進行序列化和反序列化的工具,上面的序列化和反序列化都是我們自己寫的,而現在我們可以直接使用 JSON 幫我們完成序列化和反序列化。

(1)安裝 JSON 庫

我們在 C++ 中想要使用 JSON,首先需要安裝 jsoncpp 第三方庫,在我們的云服務器上執行指令 sudo yum install jsoncpp-devel -y 即可。

安裝成功后,我們可以通過 ls /usr/include/jsoncpp/json/ 查看到我們需要的頭文件,下面我們使用到的是 json.h,但是系統默認的搜索路徑是 /usr/include/,所以我們可以在包頭文件的時候帶上路徑,也可以在編譯選項中添加。

在這里插入圖片描述

我們也可以在 /lib64/libjsoncpp.so 路徑下找到 JSON 的第三方庫,如下:

在這里插入圖片描述

(2)測試 JSON

下面我們簡單使用一下 JSON,我們先使用一下序列化的功能:

				int main(){Json::Value root;root["x"] = 10;root["y"] = 20;root["op"] = '*';Json::FastWriter w;string res = w.write(root);cout << res << endl;return 0;}

如上代碼,我們創建一個 Value 萬能對象,然后建立 k-v 映射關系,接下來創建一個 FastWriter 的對象,調用對象中的 write() 方法即可進行序列化,結果如下:

在這里插入圖片描述

另外,在序列化的時候,我們還可以創建 StyledWriter 的對象,這種是按照特定風格形成的字符串,如下:

在這里插入圖片描述

接下來我們進行反序列化,代碼如下:

				int main(){Json::Value root;root["x"] = 10;root["y"] = 20;root["op"] = '*';Json::FastWriter w;// Json::StyledWriter w;string res = w.write(root);cout << res << endl;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();cout << "x = " << x << ", y = " << y << ", op = " << op << endl;return 0;}

如上代碼,在反序列化中我們需要創建一個 Reader 對象,并調用對象中的 parse() 方法,該方法的第一個參數就是需要進行反序列化的字符串,第二個參數就是將反序列化后的字段需要寫入到哪個對象中,結果如下:

在這里插入圖片描述

(3)在網絡計算器中使用 JSON

下面我們對網絡版計算器的序列化和反序列化的部分進行修改,我們在該部分添加 JSON 代碼,但是我們使用的是條件編譯,可以讓我們在自己的序列化和反序列化與 JSON 之間進行平滑的切換,代碼如下:

				#pragma once#include <iostream>#include <string>#include <jsoncpp/json/json.h>// #define USE_MYSELF 1const std::string blank_space_sep = " ";const std::string protocol_sep = "\n";// 封裝報頭   "x op y" =>  "len"\n"x op y"\nstd::string Encode(std::string &content){std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;}// 提取報文   "len"\n"x op y"\n => "x op y"bool Decode(std::string& package, std::string* content){size_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);size_t len = stoi(len_str);// package = len_str + content_str + 2(\n)// size_t total_len = len_str.size() + len + 2*protocol_sep.size();size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos + 1, len);// 如果已經得到一個完整的報文,需要移除這個報文package.erase(0, total_len);return true;}// 定制協議class Request{public:Request(int x, int y, char op): _x(x), _y(y), _op(op){}Request(){}public:// 序列化bool Serialize(std::string *out){#ifdef USE_MYSELF// 構建報文的有效載荷// struct => string// "len"\n"x op y"\nstd::string s = std::to_string(_x);s += blank_space_sep;s += _op;s += blank_space_sep;s += std::to_string(_y);*out = s;return true;#elseJson::Value root;root["x"] = _x;root["y"] = _y;root["op"] = _op;Json::FastWriter w;*out = w.write(root);return true;#endif}// 反序列化bool Deserialize(const std::string &in) // "x op y"{#ifdef USE_MYSELFsize_t left = in.find(blank_space_sep);if(left == std::string::npos) return false;std::string part_x = in.substr(0, left);size_t right = in.rfind(blank_space_sep);if(right == std::string::npos) return false;std::string part_y = in.substr(right + 1);if(left + 1 != right - 1) return false;_op = in[left + 1];_x = std::stoi(part_x);_y = std::stoi(part_y);return true;#elseJson::Value root;Json::Reader r;r.parse(in, root);_x = root["x"].asInt();_y = root["y"].asInt();_op = root["op"].asInt();return true;#endif}void DebugPrint(){std::cout << "新請求構建完成: " << _x << _op << _y << "=?" << std::endl; }public:int _x;int _y;char _op;};class Response{public:Response(int result, int code): _result(result), _code(code){}Response(){}public:// 序列化bool Serialize(std::string *out){#ifdef USE_MYSELF// "len"\n"result code"\nstd::string s = std::to_string(_result);s += blank_space_sep;s += std::to_string(_code);*out = s;return true;#else Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter w;*out = w.write(root);return true;#endif}// 反序列化bool Deserialize(const std::string &in)     // "result code"{#ifdef USE_MYSELFsize_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part_left = in.substr(0, pos);std::string part_right = in.substr(pos + 1);_result = std::stoi(part_left);_code = std::stoi(part_right);return true;#elseJson::Value root;Json::Reader r;r.parse(in, root);_result = root["result"].asInt();_code = root["code"].asInt();return true;#endif}void DebugPrint(){std::cout << "結果響應完成, result: " << _result << ", code: "<< _code << std::endl; }public:int _result;int _code; // 表示結果的準確性};

我們可以通過在編譯選項中加上宏定義的選項,使我們更方便地選擇哪種序列化和反序列化的方式,例如 makefile 文件中:

				.PHONY:allall:servercal clientcalFlag=-DUSE_MYSELF=1Lib=-ljsoncppservercal:ServerCal.ccg++ -o $@ $^ -std=c++11 $(Lib) $(Flag)clientcal:ClientCal.cc g++ -o $@ $^ -std=c++11 $(Lib) $(Flag).PHONY:clean clean:rm -f servercal clientcal

我的博客即將同步至騰訊云開發者社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1can36hco3ehk

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/712953.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/712953.shtml
英文地址,請注明出處:http://en.pswp.cn/news/712953.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Javaweb之SpringBootWeb案例之自動配置以及常見方案的詳細解析

3.2 自動配置 我們講解了SpringBoot當中起步依賴的原理&#xff0c;就是Maven的依賴傳遞。接下來我們解析下自動配置的原理&#xff0c;我們要分析自動配置的原理&#xff0c;首先要知道什么是自動配置。 3.2.1 概述 SpringBoot的自動配置就是當Spring容器啟動后&#xff0c…

OLLAMA 本地模型調用

Ollama 網址下載 再cmd&#xff0c;用 library 里面的庫 英文對話&#xff1a; Gemma is available in both 2b and 7b parameter sizes: ollama run gemma:2bollama run gemma:7b (default) 中文對話 ollama run qwen:0.5bollama run qwen:1.8b 用vscode而不是cmd調用 …

【論文筆記】An Effective Adversarial Attack on Person Re-Identification ...

原文標題&#xff08;文章標題處有字數限制&#xff09;&#xff1a; 《An Effective Adversarial Attack on Person Re-Identification in Video Surveillance via Dispersion Reduction》 Abstract 通過減少神經網絡內部特征圖的分散性攻擊reid模型。 erbloo/Dispersion_r…

強化學習嵌入Transformer(代碼實踐)

這里寫目錄標題 ChatGPT的答案GPT4.0 ChatGPT的答案 # 定義Transformer模塊 class Transformer(nn.Module):def __init__(self, input_dim, hidden_dim, num_heads, num_layers):super(Transformer, self).__init__()self.encoder_layer nn.TransformerEncoderLayer(d_modeli…

Vue3中組件通訊的方式

Vue3中組件通訊的方式 1 &#x1f916;GPT&#x1f916;: (答案有點問題混淆了vue2的內容) 父組件向子組件傳遞數據 props 子組件通過 props 屬性從父組件接收數據。emit事件子組件通過emit 事件 子組件通過 emit事件子組件通過emit 發射事件向父組件發送消息。provide / in…

Java SpringCloud gateway面試題

Java SpringCloud gateway面試題 前言1、什么是網關Zuul&#xff08;gateway&#xff09;&#xff1f;2、服務網關的作用&#xff1f;3、Zuul網關(Gateway)如何搭建集群&#xff1f;4、ZuulFilter常用有那些方法&#xff1f;5、如何實現動態zuul網關路由轉發&#xff1f;6、在Z…

kubeadm安裝部署

目錄 1.要求 2.環境準備 3.所有節點安裝docker 4.所有節點安裝kubeadm&#xff0c;kubelet和kubectl 5.部署K8S集群 6.測試 7.擴展3個副本 8.部署Dashboard master&#xff08;2C/4G&#xff0c;cpu核心數要求大于2&#xff09;192.168.27.10docker、kubeadm、kubelet、…

LightDB - ecpg 支持dml 中使用 return into 【24.1】

在之前的版本中ecpg 中只能使用returning into 來給c 變量賦值&#xff0c;如下&#xff1a; exec sql update t1 set c aa where id 2 returning c into :c_val;為了兼容oracle pro*c 中return into 的用法&#xff0c;從24.1 開始&#xff0c; LightDB 也支持通過return in…

Chrome插件 | WEB 網頁數據采集和爬蟲程序

無邊無形的互聯網遍地是數據&#xff0c;品類豐富、格式繁多&#xff0c;包羅萬象。數據采集&#xff0c;或說抓取&#xff0c;就是把分散各處的內容&#xff0c;通過各種方式匯聚一堂&#xff0c;是個有講究要思考的體力活。君子愛數&#xff0c;取之有道&#xff0c;得注意遵…

mobile app 安全掃描工具MobSF了解下

可以干啥&#xff1a; static 靜態分析 dynamic 動態分析 可以用來滲透了 如何docker安裝 docker image 下載地址https://hub.docker.com/r/opensecurity/mobile-security-framework-mobsf/ setup 兩行即可 1 docker pull opensecurity/mobile-security-framework-mobsf…

關于VScode遠程編寫linux SHELL的報錯處理

使用vscode遠程編寫linux保存shell時&#xff0c;提示報錯&#xff1a; 未能保存“shell”: 無法寫入文件"vscode-remote:.../tmp/shell"(NoPermissions (FileSystemError): Error: EACCES: permission denied, open /tmp/shell) 大體意思是說&#xff1a;權限被拒…

Python | 從子目錄文件導入父目錄模塊的方法

問題描述 我有兩級目錄&#xff0c;第一級稱為parent_dir&#xff0c;第二級稱為child_dir。現在在child_dir下&#xff0c;有一個py&#xff0c;稱為child.py&#xff0c;在parent_dir下&#xff0c;也有一個py&#xff0c;稱為parent.py。 我想從child.py中導入parent.py中…

Go Slice的底層實現原理深度解析

文章目錄 切片的誕生&#xff1a;數組的延伸切片的結構初始化切片 切片的內存管理擴容機制 實例分析&#xff1a;切片的動態特性切片與性能性能對比 切片的并發安全并發場景下的切片操作 切片與接口切片與空接口 切片的遍歷與操作遍歷切片切片的切片操作 切片的垃圾回收切片的生…

年輕人怎么搞錢?

年輕人想要搞錢&#xff0c;可以考慮以下幾個方面&#xff1a; 1. 創業&#xff1a;年輕人可以通過自己的創意&#xff0c;找到一個市場的空缺&#xff0c;開創自己的業務。可以從比較小的項目開始&#xff0c;逐漸擴大范圍&#xff0c;積累經驗和財富。 2. 投資&#xff1a;…

成為大佬之路--linux軟件安裝使用第000000021篇--linux安裝docker

簡介 Docker 是一個開源項目&#xff0c;誕生于 2013 年初&#xff0c;最初是 dotCloud 公司內部的一個業余項目。它基于 Google 公司推出的 Go 語言實現。 項目后來加入了 Linux 基金會&#xff0c;遵從了 Apache 2.0 協議&#xff0c;項目代碼在 [GitHub](https://github.co…

Hadoop之HDFS——【模塊二】數據管理

一、Namespace的概述 1.1.集群與命名空間的關系 類似于大集群與小集群之間的關系,彼此之間獨立又相互依存。每個namespace彼此獨立,Namespace工作時只負責維護本區域的數據,同時所有的namespace維護的文件都可以共用DataNode節點,為了區分數據屬于哪些Namespace,DataNode…

強大而靈活的python裝飾器

裝飾器&#xff08;Decorators&#xff09; 一、概述 在Python中&#xff0c;裝飾器是一種特殊類型的函數&#xff0c;它允許我們修改或增強其他函數的功能&#xff0c;而無需修改其源代碼。裝飾器在函數定義之后立即調用&#xff0c;并以函數對象作為參數。裝飾器返回一個新…

力扣151--反轉字符串中的單詞(優)

清晰易懂&#xff0c;簡單高效&#xff01; 大體思路&#xff1a; 每次截取到想要的單詞&#xff0c;拼接到新的sb中&#xff0c;過程中伴隨雙指針進行空格位置指向控制&#xff0c; 其中如果start指針如果0的情況要放在第一個判斷條件防止邊界條件失效&#xff0c;并且這種…

Linux系統運維腳本:shell腳本查看一定網段范圍在線網絡設備的ip地址和不在線的網絡設備的數量(查看在線和不在線網絡設備)

目 錄 一、需求說明 二、解決方案 &#xff08;一&#xff09;解決思路 &#xff08;二&#xff09;方案 三、腳本程序實現 &#xff08;一&#xff09;腳本代碼和解釋 1、腳本代碼 2、代碼解釋 &#xff08;二&#xff09;腳本驗證 1、腳本編輯…

CrossOver 24下載-CrossOver 24 for Mac下載 v24.0.0中文永久版

CrossOver 24是一款可以讓mac用戶能夠自由運行和游戲windows游戲軟件的虛擬機類應用&#xff0c;雖然能夠虛擬windows但是卻并不是一款虛擬機&#xff0c;也不需要重啟系統或者啟動虛擬機&#xff0c;類似于一種能夠讓mac系統直接運行windows軟件的插件。它以其出色的跨平臺兼容…