【LINUX網絡】應用層自定義協議與序列化——通過實現一個簡單的網絡計算器來體會自定義協議

????????在了解了各種協議的使用以及簡單的socket接口后,學會了“怎么傳”的問題,現在來了解一下“傳什么”的問題。

1. 序列化與反序列化

????????在前面的TCP、UDP的socket api 的接口, 在讀寫數據時, 都是按 "字符串" 的方式來發送接收的. 如果我們要傳輸一些 "結構化的數據" 怎么辦呢?
? ? ? ? 在最初的對網絡的整體結構的學習中,我們了解了網絡分層的概念,也知道了消息在傳輸的時候是會被分段的——用于描述信息的叫報頭,實際傳輸的內容叫報文,但是我們前面的demo代碼都是直接把消息當作一個個直接的string當作信息傳來傳去,沒有所謂報頭或者序列化的概念,這是很不嚴謹的。

協議不僅僅是TCP或UDP等傳輸協議,傳輸的內容也是可以被定義的

????????在同一臺機器內,結構體的“打包”與“解包”由同一套編譯器和運行時完成,直接按字節傳遞即可(比如在本地電腦的文件讀寫,就是直接二進制入再二進制出);一旦跨過網絡,就可能遇到不同操作系統、不同 CPU 體系結構帶來的字節序、對齊方式等差異,貿然按原樣發送結構體極易出錯,因此網絡通信中不宜直接傳遞裸結構體(也就是避免直接傳二進制)。

????????譬如:結構體對齊方法可能不一樣,客戶端可能是安卓平臺等等

????????既想保留結構化信息的便利,又要回避兼容性問題,業界給出的答案是序列化:把結構體按既定規則轉成一段無歧義的字符串(字節流)。接收方再通過反序列化,把這串字節重新還原成結構體。序列化與反序列化互為逆過程,屏蔽了底層差異。

????????為了讓兩端都能準確還原數據,雙方必須持有同一份“數據藍圖”——即完全一致的類型定義(也就是雙方要有一樣的協議)。這份共享的結構體定義就是應用層協議本身:它既描述了報文的字段順序、類型與含義,又隱含了編碼/解碼規則;因其隨應用程序一起部署,故屬于應用層協議范疇。

比如要傳以上的data,可以先寫成{1,2,'+'},應用層傳輸這個字符串,在服務器接受到這個字符串之后按照相同的規則進行反序列化


2.?如何理解socketfd全雙工

前面都提到UDP和TCP是全雙工的,如何理解一個fd支持同時讀寫呢?

? ? ? ? 本質是因為TCP的底層有兩個緩沖區,一個是發送緩沖區,一個是接受緩沖區。

? ? ? ? 就像OS傳輸文件給磁盤一樣,read/write/send/recv等系統調用只負責把內容發送到緩沖區,至于緩沖區多久刷新、如何刷新,都是由TCP或UDP的Kernel代碼自動進行的。

而發送和接受的本質就是拷貝,所以其實就是應用層對于內核的拷貝

? ? ? ? 所以,所謂的全雙工本質就是利用兩個緩沖區,客戶端的發送緩沖區對應服務端的接收緩沖區,服務端的發送緩沖區對應客戶端的接收緩沖區

????????不管是客戶端還是服務器,OS內部都可能積累大量的報文,操作系統需要對這些報文進行管理,管理就必須先組織。

所以內部一定有對應的結果體在描述這些報文。

觀察、了解報文是如何被管理的?

? ? ? ? 每個fd指向的struct file中都有一個隱藏的private_data指針,作為VFS的描述普通文件的struct file時,private_data沒有明確的指向。

private_data

指向具體文件系統或驅動的私有數據

但是當file作為一個套接字的描述結構體時,private_data指向的就是一個socket結構體,而socket結構體中也有一個struct file指回 file。

然后這個socket還包括了一個sock結構體,sock結構體里包含了兩個隊列,這兩個隊列里裝的都是sk_buff

sk_buff就是管理報文的。

各個報文以鏈表形式被組織管理起來。而這些鏈表就由sock中的接受隊列和寫隊列分別管理。

由此,TCP\UDP等就能進行全雙工了。

????????現在將視角集中到客戶端向服務端發送的一條信息之上,因為TCP是面向字節流的,所以在客戶端給服務端發送數據時可能存在發送的數據只有待發送數據的一半甚至更少,那這樣服務端接收到數據就屬于不完整的數據,在上面應用層轉換時也就可能轉換失敗。

????????基于這個原因,所以說TCP的讀寫,不論是使用文件流的read和write,還是網絡中的recv和send都是不完善的,因為這些接口不會檢測數據是否是上層需要的有效數據,而且這些接口也無法做到判斷數據是否是上層需要的有效數據,所以這就需要應用層自己判斷收到的數據是否是可以被正確轉換的,如果不是就應該繼續接收直到至少有一條有效數據。

????????TCP更像自來水,自來水公司只負責把水放到你家的水箱里,你自己可能一桶一桶接,可能一杯一杯接。TCP按照真實情況,控制著一點一點發,所以需要由應用層來控制報文的完整性。因此,TCP中必須要有序列化和反序列化的操作。

????????但是對于UDP來說就不存在上面TCP這個問題,因為UDP是面向數據包的,所謂數據包就是將數據整個打包,在發送時要么就發整個數據包,要么就一點也不發,這樣不論是哪一個接口,拿到的都是完整的數

?????????而UDP就是發快遞。永遠都是完整的一個包裹,快遞員不被允許送半個包裹給你。

JSONCPP?

實際數據什么時候發,發多少,出錯了怎么辦,由 TCP 控制,所以 TCP 叫做傳輸控制協議。
所以,要把這個結構體給控制成什么樣子才作為標準呢?
我們可以自己制定,也有一些被規定好并且比較有名的方案:
其中包含 xml json protobuf
一句話理解:
? XML:「文檔+元數據」時代的老大哥,現在只做配置/協議兼容。
? JSON:「前后端通用語」,無 schema,想改就改,調試最爽。
? Protobuf:「高性能 RPC 專用二進制」,IDL 一把梭,版本演進最省心。

作為后端開發者,我們重點學習jsoncpp插件的使用 :

Jsoncpp
Jsoncpp 是一個用于處理 JSON 數據的 C++ 庫。它提供了將 JSON 數據序列化為字
符串以及從字符串反序列化為 C++ 數據結構的功能。Jsoncpp 是開源的,廣泛用于各
種需要處理 JSON 數據的 C++ 項目中
簡單介紹:
1.
簡單易用:Jsoncpp 提供了直觀的 API,使得處理 JSON 數據變得簡單。
2.
高性能:Jsoncpp 的性能經過優化,能夠高效地處理大量 JSON 數據。
3.
全面支持:支持 JSON 標準中的所有數據類型,包括對象、數組、字符串、數
字、布爾值和 null
4.
錯誤處理:在解析 JSON 數據時,Jsoncpp 提供了詳細的錯誤信息和位置,方便
開發者調試。
當使用 Jsoncpp 庫進行 JSON 的序列化和反序列化時,確實存在不同的做法和工具類
可供選擇。

以下是三種常見用法(JSON組件只要會用就行,不需要掌握很多,忘記了就AI)

使用 Json::Value toStyledString 方法:
優點:將 Json::Value 對象直接轉換為格式化的 JSON 字符串。

?實例如下:

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();std::cout << s << std::endl;return 0;
}$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}//第一種,使用toStyledString。直接把一個JSON::VALUE對象轉換成string:
使用 Json::StreamWriter
優點:提供了更多的定制選項,如縮進、換行符等。
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter 的工廠std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}$ ./test.exe
{
"name" : "joe",
"sex" : "男"
}

這次的代碼示例中我們沒有展示如何定制,不過AI之后就可以了解到:

反序列化:

????????反序列化指的是將序列化后的數據重新轉換為原來的數據結構或對象。Jsoncpp 提供
了以下方法進行反序列化:
1.
使用 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;
}
$ ./test.exe
Name: 張三
Age: 30
City: 北京

?在今天的demo代碼中,我們采取部分自定義+JSON

????????????????????????????????

可以避免1+22+3的歧義,不知道是1+2? 2+3還是1+22+3

3. 網絡計算器

網絡計算器:
????????上面已經基本介紹了一些概念,下面基于TCP實現一個網絡計算器,通過這個計算器更深刻得去理解上面的概念

????????網絡計算器的基本功能就是客戶端發送計算表達式(本次只實現五種運算,分別是:+、-、*、/和%),服務端接收到計算表達式后通過相關接口對這個表達式進行處理并將結果返回給客戶端

現在就來構思這個網絡計算器,如何通過協議模塊以及之前的TCP框架進行傳輸。

今天的demo都是基于【LINUX網絡】使用TCP簡易通信-CSDN博客中實現的TCP框架進行的

socket code of TCP demo · 78028f9 · lsnmjp/code of cpp Linux 算法 - Gitee.com

使用JSON進行序列化?

????????很明顯,客戶端傳過去的是諸如“1+2”,服務器要傳回去的是“3,正確計算”或者“0xfffff,非正常計算”等字段。

? ? ? ? 所以,需要把這兩種數據都進行結構化,一個是class Request,另一個是class Resluat

客戶端生成Req,經過序列化之后傳到服務器,服務器經過反序列化獲得Req,丟給運算邏輯函數,運算邏輯函數會返回Res需要的數據,再生成一個Res之后經過序列化傳給客戶端。

并且,兩個類還需要搭配相應的序列化函數和反序列化函數。

????????

?前面我們提到了,應用層需要我們自行進行檢查,得到的報文是不是完整的(read或者recv得到的不一定是完整的一個Res或者Req),所以其實在設計應用層時,到時候還需要設計類似的檢測“報頭”的函數

編碼來看看細節:

所以到時候在main函數里大概是:

Request req(10,20,"+"); string str; req.Serialize(str); 相當于str是一個輸出型參數

寫進去的時候自動判斷是什么類型的,拿出來的時候需要手動指定是什么類型的

bool Deserialize(std::string& in_string){//反序列化Json::Value root;Json::Reader reader;bool ParseSuccess = reader.parse(in_string,root);if(!ParseSuccess){LOG(LogLevel::ERROR)<<"Parse Failed";return false;}//使用Json數據_res = root["res"].asInt();_success = root["y"].asBool();return true;}

簡單一個測試

#include "Protocol.hpp"
#include <iostream>
#include <string>int main()
{Request req(10,20,'+');std::string out;req.Serialize(&out);std::cout<<out<<std::endl;req.Deserialize(out);req.Print();return 0;
}

序列化得到了報文,現在為了在應用層區分每一條消息(一次完整的x和y的計算),我們使用一個Encode函數和Decode函數來添加、取消報頭,希望我們的每一次完整格式都是:

12\r\n{JSON}\r\n? ? 、 34\r\n{JSON}\r\n? ?其中,前面的數表示后面JSON串的長度

應用層添加報頭?

Encode可以給每一個配置好的JSON串添加報頭:

?

為了增加代碼的健壯性,大概處理下Decode中可能出現的各種問題:

1.避免可能整個包不完整的情況

? ? ? ? 通過計算一個full_length來避免一條報文過于短

2.避免壓根沒找到Sep

? ? ? ? 通過判斷pos來決定。? ? ? ??

3. 有可能送了好幾條完整的報文,需要能剔除前面的完整的、已經被獲取的報文

諸如:12\r\n{JSON}\r\n

?回歸Server.hpp。對于recv函數,由于tcp通信的特性,放到inbuffer里的可能是半個Request(序列化后的JSON串),可能是一個,也可能是多個。同樣,下面的send也是不完善的

那么,我們是不是需要一個package用來存每一輪接受到inbuffer里的內容,再對這個package進行解析,拿走完整的JSON,讓留在package里面的半個json等待下一輪的inbuffer傳進來。

當然,HandleRequest作為“網絡計算器”這個程序的在網絡通信層中處理任務的模塊,肯定不該被用于處理類似于“package是不是不完善”的問題,只管把這個package丟給中間層就可以了?

只需要知道,_handler返回的也一定是一個被序列化的Response結構體,所以這個_handler不應該直接傳給計算器層,應該傳給一個用于解析的中間層。不過,如果是調用別人的庫的話,這種中間層都是應該直接被寫好的,只不過我們今天是純手搓,所以必須實現這一層Decode和Desiralize


實現一下計算器

計算器的邏輯就不過多贅述了。

這個Calculator的參數和返回值就很能說明序列化的必要性,只針對兩個結構進行運算

????????

此處的計算器本身的業務邏輯應該就不需要多說了:


將計算功能注冊進入服務中

注冊進入之后,保證整個TCP層就只需要負責IO了。

using Cal_t = std::function<Response(Request)>;class Parse
{
public:Parse(Cal_t cal): _cal(cal){}std::string Parse2Entry(std::string &package){std::string message; // package解包之后的信息// 1.解包bool ret = Decode(package, &message);if (!ret || message.empty()){// 如果Decode失敗,返回空串,這樣在Server.hpp中就可以去重新recvreturn std::string();}// 2.反序列化Request req;if (!req.Deserialize(message)){LOG(LogLevel::ERROR) << "反序列化失敗";return std::string();}// 3.計算Response ans;ans = _cal(req);// 4.序列化std::string message_back;if (!ans.Serialize(&message_back)){LOG(LogLevel::ERROR) << "序列化失敗";}// 5.添加報頭if (!Encode(message_back)){LOG(LogLevel::ERROR) << "Encode failed";}}private:Cal_t _cal;
};

如上述代碼,解碼、解析等工作主要就是靠這個Decode

之前寫的Decode就有這個功能:1、探測報文完整性。2、報文完整就提取出來

保證返回為true,并且content不為空。

解碼成功的時候:

如果Decode失敗,或者message是空,那么我們就返回一個空串。

而一旦返回一個空串(對于服務器)

就會執行continue,從而繼續recv

這也體現了package+=的意義,如果在_handler中package沒有被處理,那么我們就可以通過+=

從而拿到完整的報文?

一旦拿到這個message,此時的message就是一個曾經被序列化的request

為了健壯性,如果反序列化失敗,還是要返回一個空串

????????

走到最后一步,就是計算(也可以像演示中的代碼那樣,直接using一個新的函數類別,這樣能形成類之間的解耦合)

現在要返回的是一個Response的結果,應以被序列化過的狀態去返回

?

所以現在的整個代碼就形成了三層:計算器、分析、服務器

package處理多個完整JSON

????????如果 一個package里有多個完整的請求該怎么辦呢?還需要簡單修改一下剛剛的parse邏輯。

????????package有點像生產消費隊列中的生產者

只要package不為空,就可以一直去decode package,不過也因此package必須要傳引用
?

std::string Parse2Entry(std::string &package){std::string message; // package解包之后的信息std::string return_response_str;// 1.解包while (!Decode(package, &message)){if (message.empty()){// 如果Decode失敗,返回空串,這樣在Server.hpp中就可以去重新recvreturn std::string();}// 2.反序列化Request req;if (!req.Deserialize(message)){LOG(LogLevel::ERROR) << "反序列化失敗";return std::string();}// 3.計算Response ans;ans = _cal(req);// 4.序列化std::string message_back;if (!ans.Serialize(&message_back)){LOG(LogLevel::ERROR) << "序列化失敗";}// 5.添加報頭if (!Encode(message_back)){LOG(LogLevel::ERROR) << "Encode failed";}return_response_str+=message_back;}//可能是多個JSON拼接的return_response_strreturn return_response_str;}


主程序

現在的主程序就非常清晰了,只需要一層一層的使用lambda綁定進去就可以了

注意,給tcp_server綁定的時候package也必須傳引用,否則還是存在不能解決多個JSON串的問題。


客戶端(簡化)

客戶端就可以直接按照序列化、Decode的順序來做:

注意,第四步的位置應該是要加循環的,必須保證recv到了一個完整的、可以被Decode的字符串才行

簡單測試一下:

-------------------------------------------------------code end--------------------------------------------------------------


4. 再看OSI七層模型與TCP/IP四層協議

在比較 OSI 七層模型和 TCP/IP 模型時,我們可以觀察到兩者在低四層上是相同的。這種一致性的本質原因在于,這四層的功能是可以通過操作系統實現的。為了確保網絡通信的順暢進行,這四層的實現必須是統一的。

然而,當我們將目光轉向 OSI 七層模型的上三層時,情況就有所不同了:

????????

  1. 會話層(Session Layer):這一層主要負責通信管理,定義了客戶端和服務器之間如何進行通信。這一功能的實現依賴于操作系統底層的接口,因此,會話層實際上是對下四層通信的管理和協調。在網絡計算器的設計中,這一層對應于客戶端和服務端的通信設計。再直白一點,這一層就是使用TCP這些接口的代碼

  2. 表示層(Presentation Layer):一旦客戶端和服務端能夠正常通信,接下來的關鍵問題就是確定通信的具體內容。表示層負責設定傳輸內容的格式,確保雙方能夠識別并正確解析彼此的數據。在網絡計算器的實現中,這一層對應于序列化和反序列化的過程,以及編碼和解碼的操作。這一層就是parse層次

  3. 應用層(Application Layer):最后,我們需要定義傳輸的內容,即結構化數據的設置。在網絡計算器中,這一層對應于請求類和響應類的字段定義。這一層就是計算器

這三層緊密相連,缺少任何一層都會導致通信無法正確進行。TCP/IP 協議將 OSI 模型的這三層合并為一層的原因在于,這些功能無法由操作系統具體實現,它們屬于操作系統之上的應用層面。這種合并簡化了模型,同時保持了網絡通信的核心功能。

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

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

相關文章

電腦一鍵重裝系統win7/win10/win11無需U盤(無任何捆綁軟件圖文教程)

建議還是使用U盤進行重裝系統&#xff0c;如果暫時還不會沒有U盤&#xff0c;那就按照我這個來吧。 一&#xff0c;工具下載&#xff1a; 一鍵重裝工具 密碼:g5p3 二&#xff0c;鏡像下載: 鏡像站點&#xff1a;MSDN, 我告訴你 - 做一個安靜的工具站 可以下載需要重裝的系統…

深入探索Supervision庫:Python中的AI視覺助手

深入探索Supervision庫&#xff1a;Python中的AI視覺助手 在計算機視覺和機器學習領域&#xff0c;數據處理和結果可視化是項目成功的關鍵環節。今天我們將深入探討一個強大的Python庫——Supervision&#xff0c;它專為簡化AI視覺項目的工作流程而設計。 什么是Supervision&am…

面向對象之類、繼承和多態

系統是由匯總了數據和過程的“對象”組成的。在面向對象中&#xff0c;軟件被定義為“類”&#xff0c;然后創建“實例”并運行。系統是通過“實例”之間的互相交換“消息”而運行的&#xff0c;但由于進行了“封裝”&#xff0c;所以無法查看內部的詳細內容&#xff0c;這被稱…

傳統防火墻與下一代防火墻

防火墻的發展過程第一種簡單包過濾防火墻工作于&#xff1a;3、4層實現了對于IP、UDP、TCP信息的一些檢查優點&#xff1a;速度快、性能高、可用硬件實現&#xff1b;兼容性較好檢查IP、UDP、TCP信息缺點&#xff1a;安全性有限&#xff1a;僅能基于數據包的表面層面進行審查&a…

計算機視覺前言-----OpenCV庫介紹與計算機視覺入門準備

前言&#xff1a;OpenCV庫介紹與計算機視覺入門 OpenCV概述 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一個開源的計算機視覺和機器學習軟件庫&#xff0c;由Intel于1999年首次發布&#xff0c;現由非盈利組織OpenCV.org維護。它包含了超過2500種…

AI面試系統助手深度評測:6大主流工具對比分析

導語&#xff1a;秋招季&#xff0c;企業如何破局高效招聘&#xff1f;隨著2024年秋招季臨近&#xff0c;企業招聘壓力陡增。據牛客調研數據顯示&#xff0c;74.2%的求職者已接觸過AI面試&#xff0c;89.2%的企業認為AI顯著提升了篩選效率。然而&#xff0c;面對市場上琳瑯滿目…

浮雕軟件Artcam安裝包百度云網盤下載與安裝指南

如你所知&#xff0c;ArtCAM是一款專業的CAD/CAM軟件工具&#xff0c;主要用于三維浮雕設計、珠寶加工及CNC數控雕刻&#xff0c;可將二維構思快速轉化為三維藝術產品&#xff0c;深受使用者的喜愛。一、主要應用領域?工藝品與制造業?&#xff1a;木工雕花、標牌制作、模具制…

六邊形架構模式深度解析

在分布式系統設計領域&#xff0c;六邊形架構&#xff08;Hexagonal Architecture&#xff0c;又稱端口與適配器模式&#xff09;作為一種以領域為中心的架構模式&#xff0c;通過明確分離核心業務邏輯與外部交互&#xff0c;有效提升系統的可測試性、可擴展性與可維護性。本文…

Beelzebub靶機

一、主機發現 arp-scan掃描一下局域網靶機 二、信息收集 nmap -sV -A -T4 -p- 192.168.31.132 22端口ssh服務和80端口web服務是打開的 目錄掃描 三、滲透測試 訪問一下web服務是個apache的首頁 web頁面分析 有一個很奇怪的地方&#xff0c;index.php明明是一個200的響應&a…

目前常用于視頻會議的視頻編碼上行/下行帶寬對比

視頻編碼上行/下行帶寬對比H.264、VP8和VP9在不同終端數量下的上行與下行帶寬需求差異&#xff08;單位&#xff1a;Mbps&#xff09;編碼效率說明H.264基準編碼標準&#xff0c;上行和下行帶寬需求相對較高&#xff0c;硬件兼容性最佳VP8開源編碼&#xff0c;上行和下行帶寬均…

CrewAI ——構建多智能體協作的框架

CrewAI 是一個用于構建多智能體協作的框架&#xff0c;它的核心目標是通過協調多個智能體&#xff08;Agents&#xff09;來完成復雜任務。這些智能體不僅可以在單一任務中進行合作&#xff0c;還可以在動態、開放的環境中進行交互與協作。CrewAI 的設計和實現使得智能體之間能…

【數據結構初階】--排序(五)--計數排序,排序算法復雜度對比和穩定性分析

&#x1f525;個人主頁&#xff1a;草莓熊Lotso &#x1f3ac;作者簡介&#xff1a;C研發方向學習者 &#x1f4d6;個人專欄&#xff1a; 《C語言》 《數據結構與算法》《C語言刷題集》《Leetcode刷題指南》 ??人生格言&#xff1a;生活是默默的堅持&#xff0c;毅力是永久的…

InfluxDB 數據備份與恢復高級策略(二)

案例實戰&#xff1a;InfluxDB 數據備份恢復業務場景描述假設我們正在參與一個大型的物聯網項目&#xff0c;該項目涉及分布在不同區域的數千個傳感器設備 &#xff0c;這些設備實時采集環境溫度、濕度、設備運行狀態等數據&#xff0c;并將這些數據存儲在 InfluxDB 數據庫中。…

sqli-labs通關筆記-第36關GET寬字符注入(單引號閉合 手工注入+腳本注入 3種方法)

目錄 一、轉義函數 1、mysqli_real_escape_string 2、addslashes 3、轉義區別 二、寬字符注入 三、sqlmap之tamper 四、sqlmap之unmagicquotes 五、源碼分析 1、代碼審計 2、SQL注入安全性分析 六、滲透實戰 1、進入靶場 2、id1探測 3、id-1探測 4、id1%df and…

手撕設計模式——咖啡點單系統之裝飾模式

手撕設計模式——咖啡點單系統之裝飾模式 1.業務需求 ? 大家好&#xff0c;我是菠菜啊&#xff0c;好久不見&#xff0c;今天給大家帶來的是——裝飾模式。老規矩&#xff0c;在介紹這期內容前&#xff0c;我們先來看看這樣的需求&#xff1a;現在有一個咖啡館&#xff0c;有…

LRU Cache緩存替換算法

目錄 一、LRU 是什么&#xff1f;Cache是什么&#xff1f; 二、LRU Cache的實現 三、源碼 一、LRU 是什么&#xff1f;Cache是什么&#xff1f; LRU 是 "Least Recently Used" 的縮寫&#xff0c;意思是“最近最少使用”。它是一種常用的 緩存&#xff08;Cache&…

自定義視圖:圖形與圖像的處理(二):繪圖

除了使用已有的圖片之外&#xff0c;Android應用還常常需要在運行時動態地生成圖片&#xff0c;比如一個手機游戲&#xff0c;游戲界面看上去豐富多彩&#xff0c;而且可以隨著用戶動作而動態改變&#xff0c;這就需要借助于Android的繪圖支持了。1. Android繪圖基礎:Canvas、P…

微服務、服務網格、Nacos架構與原理

Nacos架構與原理 -服務網格生態-阿里云開發者社區 ------ 該文章用于學習參考,如有侵權,請直接聯系下架 服務網格的核心職責:治理“服務通信” 包括但不限于: 功能 舉例說明 負載均衡 動態選擇服務實例 熔斷、重試 某個服務失敗時自動切換、重試 流量路由 灰度發布、藍綠…

STM32——啟動過程淺析

總&#xff1a;STM32——學習總綱 參考文件&#xff1a; STM32 MAP文件淺析-V1.1 STM32 啟動文件淺析_V1.2 Cortex-M3權威指南(中文)、ARM Cotrex-M3權威指南(英文).zip 一、Map文件解析 1.1 MDK編譯過程文件 在編譯中&#xff0c;會生成11種編譯過程文件&#xff0c;可…

區塊鏈簡介

一、區塊鏈簡介 狹義上的定義&#xff1a; 區塊鏈是一種鏈式數據結構&#xff0c;通過按時間順序將數據塊逐一連接形成。這種結構通過密碼學確保了數據的不可篡改性和不可偽造性&#xff0c;形成了一種分布式賬本技術。 廣義上的定義&#xff1a; 區塊鏈技術不僅僅是一種數據…