【Linux】:自定義協議(應用層)

朋友們、伙計們,我們又見面了,本期來給大家帶來應用層自定義協議相關的知識點,如果看完之后對你有一定的啟發,那么請留下你的三連,祝大家心想事成!

C 語 言 專 欄:C語言:從入門到精通

數據結構專欄:數據結構

個? 人? 主? 頁?:stackY、

C + + 專 欄? ?:C++

Linux 專?欄? :Linux

目錄

1. 協議

2. 自定義協議

2.1 預備工作?

3. 序列與反序列化

3.1?報頭的添加與解析

3.2 計算業務

4. 功能完善

4.1 服務端

4.2 客戶端?

5. 成熟的序列反序列化方案


1. 協議

前面說過,協議其實就是一種約定,我們實現的tcp通信時,都是按照字符串的方式進行發送的,那么對方收到的也是字符串,那么如果我們需要發送一些具體化的數據呢?

就比如:現在使用的這些聊天軟件,我們在發送數據時,有昵稱、時間、具體的消息內容,因此,在發送數據時,不僅僅是將消息內容發送過去,而是將這三樣東西發送過去了,這是一種結構化的數據;

  • 所以在發送類似與這種結構化字段的數據就要制定一種協議;
  • 協議其實就是雙方在通信時約定好的一種結構化字段;

在應用層這里我們發送時并不是直接將這個結構化的字段發送給對方:

  • 因為在應用層很可能雙方系統有所差異,對于結構體的計算不統一,導致數據的不準確;
  • 所以,在應用層這里,我們要發送結構化的字段,必須要將結構化字段進行序列化成為字節流(“字符串”),將字節流發送給對方,對方通過反序列化將字節流轉化為結構化字段;
  • 序列化的目的是為了更好的網絡發送,反序列化的目的是為了上層更好的對數據進行有效字段的提取;
  • 序列化和反序列化的方式雙方可以進行統一的約定;

2. 自定義協議

在自定協議這里我們直接實現一個網絡版本的計算器來提現一下自定義協議的過程;

我們采用分模塊來實現:

  • Socket.hpp:對網絡套接字進行封裝
  • TcpServer.hpp:實現Tcp的服務器
  • TcpServerMain.cc:測試Tcp服務器
  • TcpClientMain.cc:完成客戶端
  • Protocol.hpp:自定義協議
  • Calculate.hpp:實現計算的業務

2.1 預備工作?

既然要進行網絡通信,那么就少不了需要套接字接口,前面已經寫過好多次套接字的接口了,這里對套接字進行封裝,將服務器和客戶端各自使用的接口整合在一起,我們對封裝好的套接字提供一些我們需要的接口接口;

我們之前發送數據使用的read和write,其實還有兩個接口:


Socket.hpp:

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#define Convert(addrptr) ((struct sockaddr *)addrptr)  // 套接字中對于地址類型強轉的宏namespace Net_Work
{const static int defaultsockfd = -1;const int backlog = 5;enum // 對于一些錯誤碼的設置{SocketError = 1,BindError,ListenError,};// 封裝一個基類,Socket接口類// 設計模式:模版方法類class Socket{public:virtual ~Socket() {}virtual void CreateSocketorDie() = 0;      // 創建套接字virtual void BindSocketorDie(uint16_t port) = 0;  // 綁定virtual void ListenSocketorDie(int backlog) = 0;  // 監聽virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0; // 獲取連接virtual bool ConnectServer(std::string &serverip, uint16_t &serverport) = 0; // 建立連接virtual int GetSockFd() = 0;   // 獲取套接字virtual void SetSockFd(int sockfd) = 0;  // 設置套接字virtual void CloseSocket() = 0;   // 關閉套接字virtual bool Recv(std::string *buffer, int size) = 0;  // 讀取信息virtual void Send(std::string &send_str) = 0;    // 發送信息public:// 創建監聽套接字----Servervoid BuildListenSocketMethod(uint16_t port, int blacklog){CreateSocketorDie();BindSocketorDie(port);ListenSocketorDie(blacklog);}// 創建連接套接字---Clientbool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport){CreateSocketorDie();return ConnectServer(serverip, serverport);}void BuildNormalSocketMethod(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd){}~TcpSocket() {}void CreateSocketorDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0)exit(SocketError);}void BindSocketorDie(uint16_t port) override{struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_sockfd, Convert(&local), sizeof(local));if (n < 0)exit(BindError);}void ListenSocketorDie(int backlog) override{int n = ::listen(_sockfd, backlog);if (n < 0)exit(ListenError);}Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);uint16_t newsockfd = ::accept(_sockfd, Convert(&peer), &len);if (newsockfd < 0)return nullptr;*peerip = inet_ntoa(peer.sin_addr);*peerport = ntohs(peer.sin_port);Socket *s = new TcpSocket(newsockfd);return s;}bool ConnectServer(std::string &serverip, uint16_t &serverport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverip.c_str());server.sin_port = htons(serverport);int n = ::connect(_sockfd, Convert(&server), sizeof(server));if (n == 0)return true;elsereturn false;}int GetSockFd() override{return _sockfd;}void SetSockFd(int sockfd) override{_sockfd = sockfd;}void CloseSocket() override{if (_sockfd > defaultsockfd)::close(_sockfd);}bool Recv(std::string *buffer, int size) override{char inbuffer[size];ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);if(n > 0){inbuffer[n] = 0;*buffer += inbuffer;return true;}else if (n == 0) return false;else return false;}void Send(std::string &send_str) override{// 發送信息這里我們簡略的寫了;send(_sockfd, send_str.c_str(), send_str.size(), 0);}private:int _sockfd;};}

為了先測試一下客戶端與服務器,我們先來簡單的定制一下協議:

我們想要實現一個網絡版本的計算器,來自己實現一下自定義協議;

協議方法:

  • 請求:參數1?運算符號 參數2
  • 響應:運算結果 運算狀態(結果是否可靠)

請求和相應是兩個結構化的字段,因為今天在同一臺主機上進行測試,所以我們先直接發送結構化的字段,未來客戶端和服務器分別include這個協議,至此雙方都可以看到同一份結構化字段,這就是一種自定義的協議;

另外,我們定制好的協議我也想給他們設置一個工廠模式,為了后面方便使用;

接下來我們對客戶端和服務器進行簡單的實現,因為服務器與客戶端在前面UDP和TCP通信那里細致的說多了,這里就直接展示代碼了:

TcpServer.hpp:

?對于服務器我們想采用多線程的方式對任務進行處理;

TcpServerMain.cc:

這里對與任務函數的編寫就簡單一點,為了測試能否正常通信,以及直接傳遞結構化字段;

客戶端:

TcpClientMain.cc:

客戶端這里我們也是直接發送結構化字段;

網絡版本計算器的基本流程:

服務器啟動之后,先創建連接套接字,構建一個計算的請求,然后向服務器發起請求;

因為是本地測試,所以我們可以直接傳遞結構體,先完成基本的通信,后面再實現序列化的過程;

服務器啟動之后,先創建監聽套接字設置回調方法,然后獲取新連接,使用多線程執行任務;

基本測試:

上面的代碼通信時直接發送的是結構體字段,這種發送方式只在限定情況下可以使用,就比如我們上面的測試代碼是在本地上演示的,所以不會有什么問題,為了考慮更全面,接下來就需要進行序列、反序列操作;?

在進行序列化之前再來對TCP協議進行一下深入的概念性了解:

  • 其實我們在進行TCP通信的時候,我們使用的發送(write/send)和接收(read/recv)的接口,并不是直接將數據通過網絡發送給對方,因為這些接口是用戶層接口,在內核層雙方還會存在兩個緩沖區:一個是發送緩沖區,一個是接收緩沖區;

  • write/send和read/recv接口只是將數據從用戶緩沖區拷貝到內核緩沖區,本質就是一個拷貝函數;
  • 那么至于什么時候發送,發多少,怎么發,發送出錯了怎么辦等等,這些都不需要用戶去考慮,這是由內核決定的,換句話說是由TCP協議決定的!
  • TCP協議在進行通信的時候,將發送緩沖區中的數據通過網絡拷貝至對方接收緩沖區中,其實,是雙方的OS之間進行通信
  • 這也就解釋了,write或者read在某些條件下會發生阻塞的問題;當接收緩沖區中沒有數據時,read就會阻塞,因為他不具備接收條件;當發送緩沖區寫滿的時候write就會阻塞,因為他不具備發送條件;
  • 像上面這種有人寫就有人拿的模型其實就是一種生產者消費者模型
  • 因為雙方在接收和發送時是兩個獨立的模塊,所以可以進行同時通信,所以說TCP協議是全雙工協議;

在TCP通信中,在發送時,對方發送了多少數據,并不意味著我就要接收多少數據,這完全由TCP協議來決定;

那么這就存在一種問題:我要讀取對方發送的數據,怎么保證我就能讀到對方發送的一個完整的數據報文呢?

此時就需要明確報文與報文之前的邊界(代碼中體現)

3. 序列與反序列化

為了完整我們的代碼,我們就需要在發送數據與解析數據時進行序列與反序列化的工作;

序列反序列化也是雙方進行的一種約定,也就是自定義的一種協議;

在這里我們想定制的協議是:

  • 未來要發送數據時將結構化字段全部轉化為一個字符串“_data_x op _data_y”;
  • 這里需要注意op的長度是固定的(+ - * /)但是兩邊的操作數的長度是不固定的,所以為了反序列化更方便,我們需要添加報頭,其中報頭表示的含義就是這個字符串有效內容的長度“len_data_x op _data_y”,這個報頭就叫做報文的自描述字段;
  • 為了讓報頭和有效載荷易于區分,并且為了讓報文與報文之間易于區分,我們需要在報頭和有效載荷的中間添加特殊字符(\n),在報文末尾添加特殊字符(\n)
  • “len\n_data_x op _data_y\n”;
  • 未來在讀取報文的時候,首先讀到的就是報頭,讀到\n時就知道前面的是報頭,根據報頭所表示的有效內容的長度,再向后讀取指定大小的字符即可;
  • 這里添加\n是為了應付多種場景,我們現在的場景是四則運算,有效載荷中不可能出現\n,但是如果場景是一個聊天信息呢,里面可能會出現\n,但是這個消息的長度不可能有\n,所以用\n來區分報頭與有效載荷的邊界,當然也可以使用其他特殊字符;
  • 另外,我們在報文最后添加的\n不僅僅用于區分報文和報文之間的邊界,還可以幫助我們在寫代碼的時候打印調試;
  • 上面是對請求進行的序列化,對于響應也是一樣的“len\n_result _code”。

因為請求和相應都需要添加報頭,所以序列化與報頭我們分開處理;

Request的序列化與反序列化:

未來的客戶端與服務器都需要遵守這樣的約定來進行數據的交互與處理,這就是一種自定義的協議,有用戶來決定的;

Response序列化與反序列化:

3.1?報頭的添加與解析

添加報頭:

未來我們相對這種類型"_data_x op _data_y"的字符串添加報頭,所以我們依舊采用字符串的操作,這里就不詳細解釋了;

拼接特殊字符即可;

解析報頭:

因為我們不確定報文的完整性,所以在使用解析報頭時我們采用循環調用的方式;

首先我們需要找到區分報頭和有效載荷的特殊字符;

然后截取特殊字符前面的報頭來確定有效載荷的長度;

因為報文的不確定性,所以我們需要通過前面對有效載荷的長度以及報文長度的已知值來確定出一個完整報文的總長度;

然后根據傳入的package與這個長度比較,想要至少有一個完整的報文那么就必須大于或者等于這個總長度;

然后通過特殊字符的位置進行截取到有效載荷的信息;

然后將我們已經截取到的完整報文丟棄掉,繼續處理下一個報文;

3.2 計算業務

有了序列化與反序列化,接下來就需要對數據進行業務處理了,我們拿到數據先對數據進行處理,獲取到其中的運算符(+ - * / %),然后根據不同的運算符來截取對應的操作數執行運算,然后將結果返回,所以在使用計算業務的時候,傳入的是一個請求,返回的是一個相應;

4. 功能完善

4.1 服務端

上面實現的添加報頭與解包分用其實就可以進行通信了,但是,我們想實現的是,把發送數據和接收數據放在TcpServer底層,此時我只負責發送和接收,不管發送和接收的數據是什么,將網絡和業務進行解耦;

此時就需要對執行任務的函數進行簡單的調整,未來我們發送一個字節流,對字節流進行業務處理,然后將處理完成的結果再序列化為字節流再返回給我即可,還可以再帶一個參數,表示的是業務執行過程中是否出錯;

首先我們來實現一下這個業務處理的函數HandlerRequest,在調用時,傳入一個待處理的字節流,我們需要對這個字節流進行處理,獲取到一個完整的報文,然后對報文反序列化,將有效載荷進行業務處理,處理完成之后的結果我們需要再進行序列化以及添加報頭,然后返回出去;

緊接著我們需要在ThreadRun函數中對數據進行接收和發送的操作:

4.2 客戶端?

客戶端這里的代碼就不封裝了,直接編寫實現通信;

在客戶端這里我們首先需要構建一些需要計算的請求,然后對其進行序列化并添加報頭,然后發送給服務器:

我們可以來梳理一下這個解耦的邏輯:

?我們既然能發送,當然也可以進行讀取我們發送之后計算完成的結果,所以需要對返回的響應進行解析并反序列化得到最終的結構化字段的Response:

#include "Protocol.hpp"
#include "Socket.hpp"#include <iostream>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>using namespace Protocol;int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage: " << " serverip serverport" << std::endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 創建套接字Net_Work::Socket *conn = new Net_Work::TcpSocket();if (!conn->BuildConnectSocketMethod(serverip, serverport)){std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;}std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;// 使用工廠模式std::unique_ptr<Protocol::Factory> factory = std::make_unique<Protocol::Factory>();srand(time(nullptr) ^ getpid()); // 建立隨機數的種子const std::string opers = "+-*/%";while (true){// 構建請求,遵守協議int x = rand() % 100; // [0, 99)usleep(rand() % 1234);int y = rand() % 100; // [0, 99)char op = opers[rand() % opers.size()];// 創建請求std::shared_ptr<Protocol::Request> req = factory->BuildRequest(x, y, op);// 對請求序列化std::string request_str;req->Serialize(&request_str);std::cout << request_str << std::endl;// for teststd::string testreq = request_str;testreq += " ";testreq += "= ";// 添加報頭request_str = Encode(request_str);// 發送請求conn->Send(request_str);std::string response_str;while(true){// 讀取響應if(!conn->Recv(&response_str, 1024)) break;// 解析響應報文std::string response;if(!Decode(response_str, &response))continue;// 反序列化auto resp = factory->BuildResponse();resp->Deserialize(response);// 得到了結果std::cout << testreq << resp->GetResult() << "[" << resp->GetCode() << "]" << std::endl;break;}sleep(1);}conn->CloseSocket();return 0;
}

5. 成熟的序列反序列化方案

上面我們是手寫的序列反序列化,這樣子寫也可以,但是畢竟是我們手寫的,我們可以使用一下成熟的方案,比如:json、protobuf、xml;

我們想使用一下json來替換我們手寫的序列和反序列化;

想細致了解json的使用可以去搜一些博客看一下,這里我們先使用json進行簡單的演示:

// ubuntu 安裝jsoncppsudo apt-get install libjsoncpp-dev

演示代碼:

#include <iostream>
#include <string>#include "jsoncpp/json/json.h"int main()
{// 創建Json對象// Json::Value 萬能的類型Json::Value root;// 添加kv映射數據root["k1"] = 100;root["k2"] = 100;root["hello"] = "world";root["bit"] = 8;// 序列化Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;// 反序列化int k1, k2, bit;std::string hello;Json::Value _root;Json::Reader reader;if (reader.parse(s, _root)){k1 = _root["k1"].asInt();k2 = _root["k2"].asInt();hello = _root["hello"].asCString();bit = _root["bit"].asInt();}std::cout << k1 << " " << k2 << " " << hello << " " << bit << std::endl;return 0;
}

接下來我們就將json引入到我們的代碼中:

我們使用條件編譯,也可以將我們自己實現的序列反序列化的過程保留下來

這到這里我們的代碼已經完結了,其實也可以將服務器變成守護進程;

源碼鏈接:?https://gitee.com/yue-sir-bit/linux/tree/master/Network_version_calculator

當我們自己手寫協議之后,再回頭看一下OSI定義的七層網絡協議棧,就可以與我們本節實現的代碼可以結合起來了:

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

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

相關文章

【C++】二叉樹和堆的鏈式結構

本篇博客給大家帶來的是用C語言來實現堆鏈式結構和二叉樹的實現&#xff01; &#x1f41f;&#x1f41f;文章專欄&#xff1a;數據結構 &#x1f680;&#x1f680;若有問題評論區下討論&#xff0c;我會及時回答 ??歡迎大家點贊、收藏、分享&#xff01; 今日思想&#xff…

鴻蒙保姆級教學

鴻蒙&#xff08;HarmonyOS&#xff09;是華為推出的一款面向全場景的分布式操作系統&#xff0c;支持手機、平板、智能穿戴、智能家居、車載設備等多種設備。鴻蒙系統的核心特點是分布式架構、一次開發多端部署和高性能。以下是從入門到大神級別的鴻蒙開發深度分析&#xff0c…

關于Docker是否被淘汰虛擬機實現連接虛擬專用網絡Ubuntu 22.04 LTS部署Harbor倉庫全流程

1.今天的第一個主題&#xff1a; 第一個主題是關于Docker是否真的被K8S棄用&#xff0c;還是可以繼續兼容&#xff0c;因為我們知道在去年的時候&#xff0c;由于不可控的原因&#xff0c;docker的所有國內鏡像源都被Ban了&#xff0c;再加上K8S自從V1.20之后&#xff0c;宣布…

八股學習-JUC java并發編程

本文僅供個人學習使用&#xff0c;參考資料&#xff1a;JMM&#xff08;Java 內存模型&#xff09;詳解 | JavaGuide 線程基礎概念 用戶線程&#xff1a;由用戶空間程序管理和調度的線程&#xff0c;運行在用戶空間。 內核線程&#xff1a;由操作系統內核管理和調度的線程&…

遺傳算法+四模型+雙向網絡!GA-CNN-BiLSTM-Attention系列四模型多變量時序預測

遺傳算法四模型雙向網絡&#xff01;GA-CNN-BiLSTM-Attention系列四模型多變量時序預測 目錄 遺傳算法四模型雙向網絡&#xff01;GA-CNN-BiLSTM-Attention系列四模型多變量時序預測預測效果基本介紹程序設計參考資料 預測效果 基本介紹 基于GA-CNN-BiLSTM-Attention、CNN-BiL…

Linux怎樣源碼安裝Nginx

1. 安裝必要的依賴 在編譯 Nginx 之前&#xff0c;你需要安裝一些必要的依賴包&#xff0c;像編譯工具和庫文件等。以 CentOS 系統為例&#xff0c;可借助yum命令來安裝&#xff1a; bash sudo yum install -y gcc pcre-devel zlib-devel openssl-devel要是使用的是 Ubuntu 系…

【入門初級篇】報表基礎操作與功能介紹

【入門初級篇】報表的基本操作與功能介紹 視頻要點 &#xff08;1&#xff09;報表組件的創建 &#xff08;2&#xff09;指標組件的使用&#xff1a;一級、二級指標操作演示 &#xff08;3&#xff09;表格屬性設置介紹 &#xff08;4&#xff09;圖表屬性設置介紹 &#xff0…

【新能源汽車“心臟”賦能:三電系統研發、測試與應用匹配的恒壓恒流源技術秘籍】

新能源汽車“心臟”賦能&#xff1a;三電系統研發、測試與應用匹配的恒壓恒流源技術秘籍 在新能源汽車蓬勃發展的浪潮中&#xff0c;三電系統&#xff08;電池、電機、電控&#xff09;無疑是其核心驅動力。而恒壓源與恒流源&#xff0c;作為電源管理的關鍵要素&#xff0c;在…

在線JSON格式校驗工具站

在線JSON校驗格式化工具&#xff08;Be JSON&#xff09;在線,JSON,JSON 校驗,格式化,xml轉json 工具,在線工具,json視圖,可視化,程序,服務器,域名注冊,正則表達式,測試,在線json格式化工具,json 格式化,json格式化工具,json字符串格式化,json 在線查看器,json在線,json 在線驗…

圖片黑白處理軟件推薦

圖片黑白二值化是一款小巧實用的圖片處理軟件&#xff0c;軟件大小僅268K。 它的操作極其簡單&#xff0c;用戶只需將需要處理的圖片直接拖入軟件&#xff0c;就能實現圖片漂白效果。 從原圖和處理后的圖片對比來看&#xff0c;效果顯著。這種圖片漂白處理在打印時能節省墨水&a…

【AI知識】常見的優化器及其原理:梯度下降、動量梯度下降、AdaGrad、RMSProp、Adam、AdamW

常見的優化器 梯度下降&#xff08;Gradient Descent, GD&#xff09;局部最小值、全局最小值和鞍點凸函數和非凸函數動量梯度下降&#xff08;Momentum&#xff09;自適應學習率優化器AdaGrad&#xff08;Adaptive Gradient Algorithm&#xff09;?RMSProp&#xff08;Root M…

1.5.5 掌握Scala內建控制結構 - 異常處理

本次實戰聚焦于Scala內建控制結構中的異常處理機制。通過具體案例演示了如何使用try-catch-finally結構來處理程序運行中可能出現的異常情況。在try塊中調用可能拋出異常的方法&#xff0c;catch塊則根據不同異常類型進行捕獲并處理&#xff0c;finally塊則無論是否發生異常都會…

信息系統運行管理員教程4--信息系統軟件運維

第四章 信息系統軟件運維 信息系統軟件是信息系統運行的核心&#xff0c;其運維的目的是保證信息系統軟件能正常而可靠地運行&#xff0c;并能使系統不斷得到改善和提高&#xff0c;以充分發揮作用。 第1節 信息系統軟件運維概述 1.信息系統軟件運維的概念 信息系統軟件運維…

以光盤讀寫系統演示面向對象設計的原則與方法

面向對象設計&#xff08;OOD&#xff09;是軟件開發中的核心方法&#xff0c;強調通過對象、類、繼承、封裝和多態等概念來構建系統。以下是面向對象設計的原則、方法及常用技術手段&#xff1a; 一、面向對象設計原則&#xff08;SOLID原則&#xff09; 單一職責原則&#x…

齒輪熱處理學習筆記分享

對于一個做冷加工的人來說&#xff0c;熱處理是一個神秘的話題&#xff0c;但是一點都不去了解的話&#xff0c;工作也無法進行。所以抽點時間來學習一下齒輪熱處理相關的內容&#xff0c;做成筆記分享給愛學習的小伙伴們&#xff0c;文章較長&#xff0c;需要一些耐心去閱讀&a…

WPF 布局舍入(WPF 邊框模糊 或 像素錯位 的問題)

1. 什么是 WPF 布局舍入&#xff1f; 在 WPF 開發過程中&#xff0c;可能會遇到界面模糊、邊框錯位、文本渲染不清晰等問題。這些現象通常是由于 WPF 采用 設備無關像素&#xff08;DIP, Device Independent Pixels&#xff09;&#xff0c;在不同 DPI 設置下&#xff0c;UI 元…

Linux中vscode編程,小白入門喂飯級教程

確保Ubuntu聯網 因為后面安裝VScode需要從互聯網下載。 安裝GCC 在桌面空白處右鍵->打開終端 執行命令&#xff1a;gcc -v 在最后一行可以看到gcc version 7.5.0 如果提示Command ‘gcc’ not found&#xff0c;就查一下如何安裝gcc&#xff0c;先把gcc安裝好。 安裝VS…

Python 的 ?ORM(Object-Relational Mapping)工具淺講

SQLAlchemy相關講解 1. SQLAlchemy 是什么? ?定義:一個 Python 的 ?ORM(Object-Relational Mapping)工具,允許開發者通過 Python 類與對象操作數據庫,而非直接編寫 SQL。?核心組件: ?Core:底層 SQL 表達式語言,提供數據庫無關的 SQL 操作接口。?ORM:基于 Core …

藍橋杯真題——洛谷Day13 找規律(修建灌木)、字符串(乘法表)、隊列(球票)

目錄 找規律 P8781 [藍橋杯 2022 省 B] 修剪灌木 字符串 P8723 [藍橋杯 2020 省 AB3] 乘法表 隊列 P8641 [藍橋杯 2016 國 C] 贏球票 找規律 P8781 [藍橋杯 2022 省 B] 修剪灌木 思路&#xff1a;對某個特定的點來說有向前和向后的情況&#xff0c;即有向前再返回到該位置…

matrix-breakout-2-morpheus 靶機----練習攻略 【僅獲取shell】

【此練習僅做到反彈shell】 1.靶機下載地址 https://download.vulnhub.com/matrix-breakout/matrix-breakout-2-morpheus.ova 2. 打開靶機&#xff0c;kali使用nmap掃描同C段的主機 找到靶機ip 確保靶機和kali網卡均為NAT模式 先查看kali的ip nmap 192.168.182.1/24 …