49 序列化和反序列化

本章重點

理解應用層的作用,初識http協議
理解傳輸層的作用,深入理解tcp的各項特性和機制
對整個tcp/ip協議有系統的理解
對tcp/ip協議體系下的其他重要協議和技術有一定的了解
學會使用一些網絡問題的工具和方法

目錄

1.應用層
2.協議概念
3. 網絡計算器
4. 序列化和反序列化
5. 協議定制
6. 數據處理
7. 網絡函數封裝
8. 服務端
9. 客戶端
10.結果示例
11. json序列化
12. 添加條件選項
13.再看七層模型

1. 應用層

實際解決問題,滿足日常需求的網絡程序都在應用層

2. 協議概念

協議是一種“約定”,socket api的接口,在讀寫數據時,都是按“字符串”的方式來發送接收的,如果我們要傳輸一些結構化的數據,怎么辦?

tcp也稱作傳輸控制協議(什么時候發,發多少,出錯了怎么辦),傳輸層是在os內部實現的,是os網絡模塊部分,將數據交給tcp實際上就是交給os,由于os決定數據的發送,那么收上來的數據就不能完全確定了,有可能是完整的,也有可能是多個報文,或者一部分。所以為了成功的發送和解析報文,應用層就需要協議約定好數據的格式,確定數據的完整性,如果長度不符,就不處理

3. 網絡計算器

需要實現一個服務器的計算器,把客戶端兩個數發過去,然后由服務器計算,最后把結果返回給客戶端

約定方案

約定方案一:
客戶端發送一個形如“1+1”的字符串
這個字符串有兩個操作數,都是整形
兩個數字之間會有一個字符是運算符
數字和運算符之間沒有空格
。。。
這種情況如果一次性發送了四五組數據,無法區分是一個還是幾個報文

約定方案二:
定義結構體表示需要交互的信息
發送數據時將這個結構體按照一個規則轉換成字符串,接收到數據的時候再按照相同的規則將把字符串串轉換回結構體

// proto.h 定義通信的結構體
typedef struct Request {int a;int b;
} Request;
typedef struct Response {int sum;
} Response;
// client.c 客戶端核心代碼
Request request; 
Response response; scanf("%d,%d", &request.a, &request.b);
write(fd, request, sizeof(Request));
read(fd, response, sizeof(Response)); 
// server.c 服務端核心代碼
Request request;
read(client_fd, &request, sizeof(request));
Response response;
response.sum = request.a + request.b;
write(client_fd, &response, sizeof(response));

直接發送結構體,結構體兩邊類型一樣,這種方法可以,但是不同的設備結構體的對齊方式可能不一樣,導致同一個結構體大小不一樣。出問題后非常難調試,都是二進制的。如果一出性發好多個,也不好區分一個個報文,不過os內部是這樣實現的,各種情況都考慮了

只要保證一端發送,另一端能夠正確解析,這種約定就是應用層協議。為了更穩定,可以定下面的約定

4. 序列化和反序列化

上面的約定方式都有不足之處。最終的約定方式以qq消息舉例,需要的結構體包含消息內容,昵稱,發送時間,將這三個字符串組合為一個字符串發送給客戶,客戶收到后又重新轉換為結構體,解析為發送時間+昵稱+內容的結構,雙方的內容結構是相同的。也實現了簡單的分層,上面負責結構的規劃組成序列,下面負責將序列化的數據完整發送和接收,為了方便解析和發送,還需要規定報文之間的分隔。這樣序列化和反序列化方便網絡收發

在這里插入圖片描述

5. 協議定制

協議分兩個類,一個是請求類,一個是響應類,請求方用計算類,生成計算式,兩個操作數一個操作符,所以成員變量兩個int為操作數,一個char操作符。將數據序列化為“x 操作符 y”的結構發送,對方收到后還要提供反序列化為計算類來計算結果
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

結算結果放到結果類,兩個成員變量,一個是int的結果,一個是int的返回碼,表明結果可不可信。也要提供序列化和反序列化功能,將結果和返回碼改變為“結果 返回碼”的字符串格式

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

上面的數據可以用來發送了,但如果客戶一次性發了很多個數據,或者一個報文也不滿足。如何區分每個報文?所以必須為發出的數據添加報頭,報頭格式定為“長度\n報文\n”,有添加報頭也要解析報頭
在這里插入圖片描述
在這里插入圖片描述

protocol.hpp

#pragma once
#include <string>//分隔符
const std::string black_sep = " ";
const std::string protocol_sep = "\n";//解決報文外部格式//len\n正文\nstd::string encode(std::string& message){std::string package = std::to_string(message.size());package += protocol_sep;package += message;package += protocol_sep;return package;}//len\na + b\nbool decode(std::string& message, std::string* content){std::size_t pos = message.find(protocol_sep);if (pos == std::string::npos){return false;}std::string len_str = message.substr(0, pos);std::size_t len = std::stoi(len_str);std::size_t total_len = len_str.size() + len + 2;//檢查長度if (message.size() < total_len){return false;}*content = message.substr(pos + 1, len);//earse 移除報文message.erase(0, total_len);return true;}class Request
{
public:Request(){}Request(int a, int b, char oper){_num1 = a;_num2 = b;_op = oper;}//a + bbool serialize(std::string* out){//構建報文有效載荷std::string str;str += std::to_string(_num1);str += black_sep;str += _op;str += black_sep;str += std::to_string(_num2);*out = str;return true;}//a + bbool deserialize(std::string& in){//astd::size_t left = in.find(black_sep);if (left == std::string::npos){return false;         }std::string part_a = in.substr(0, left);// bstd::size_t right = in.rfind(black_sep);if (right == std::string::npos){return false;     }std::string part_b = in.substr(right + 1);//+if (left + 2 != right){return false;}_op = in[left+1];_num1 = std::stoi(part_a);_num2 = std::stoi(part_b);return true;}void debugprint(){cout << "新請求構建完成:" << _num1 << _op << _num2 << endl;}public:int _num1;int _num2;char _op;
};class Response
{
public:Response(){}Response(int res, int cod){_result = res;_code = cod;}//1000 0bool serialize(std::string* out){string str = std::to_string(_result);str += black_sep;str += std::to_string(_code);*out = str;return true;}//1000 0bool deserialize(std::string& in){std::size_t pos = in.find(black_sep);if (pos  == std::string::npos){return false;}std::string left = in.substr(0, pos);std::string right = in.substr(pos + 1);_result = std::stoi(left);_code = std::stoi(right);return true;}void debugprint(){cout << "結果響應完成,result:" << _result << ",code:" << _code << endl;}public:int _result;int _code;   //0可信,否則表明對應的錯誤
};

6. 數據處理

有了協議就可以實現數據處理,計算結果并封裝的類

枚舉各種計算錯誤的情況,操作符等其他問題用OTHER
在這里插入圖片描述

計算函數傳入上面的請求類,返回結果響應類。根據操作符進行不同的運算
在這里插入圖片描述
數據處理函數將收到的字符串內容轉換為請求類,調用計算函數得到結果,并對結果封包返回字符串用來發送
在這里插入圖片描述

servercal.hpp

#pragma once
#include "protocol.hpp"enum
{DIVZERO = 1,MODZERO,OTHER_OPER
};class ServerCal
{
public:ServerCal(){}Response CalculatorHelp(const Request& req){Response res(0, 0);switch (req._op){case '+':res._result = req._num1 + req._num2;break;case '-':res._result = req._num1 - req._num2;break;case '*':res._result = req._num1 * req._num2;break;case '/':if (req._num2 == 0){res._code = DIVZERO;}else{res._result = req._num1 / req._num2;}break;case '%':if (req._num2 == 0){res._code = MODZERO;}else{res._result = req._num1 % req._num2;break;}default:res._code = OTHER_OPER;break;}return res;}std::string Calcluator(std::string& package){std::string content;bool r = decode(package, &content);if (!r){return "";}Request req;r = req.deserialize(content);if (!r){return "";}req.debugprint();content = "";Response res = CalculatorHelp(req);res.debugprint();res.serialize(&content);content = encode(content); // len\n正文\nreturn content;}~ServerCal(){}
};

7. 網絡函數封裝

將服務器的socket常用功能封裝為scoke類,成員sockfd為socket函數的返回值,提供返回sockfd的函數

Socket.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include "log.hpp"enum
{SOCKERR = 1,BINDERR,LISERR
};Log lg;
const int backlog = 5;
class Sock
{
public:Sock(){}void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){lg.logmessage(fatal, "socket error");exit(SOCKERR);}}void Bind(uint16_t port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(port);int bret = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));if (bret < 0){lg.logmessage(fatal, "bind error");exit(BINDERR);}}void Listen(){int lret = listen(_sockfd, backlog);if (lret < 0){lg.logmessage(fatal, "listen error");exit(LISERR);}}int Accept(string* clientip, uint16_t* clientport){sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(_sockfd, (sockaddr*)&peer, &len);if (newfd < 0){lg.logmessage(warning, "accept error");return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const string ip, const uint16_t port){sockaddr_in peer;memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);peer.sin_port = htons(port);int cret = connect(_sockfd, (const struct sockaddr*)&peer, sizeof(peer));if (cret == -1){lg.logmessage(warning, "connect error");return false;}return true;}void Close(){close(_sockfd);}int Fd(){return _sockfd;}~Sock(){}
public:int _sockfd;
};

8. 服務端

服務端和通用服務器一樣,accept收到連接請求后,創建子進程提供服務,因為數據可能不是一次性接收完的,所以recbuff不斷加上讀到的內容。同時有可能有多個報文,所以收到數據后進入循環處理,將字符串交給函數模板對象,就是上面的數據處理函數。
在這里插入圖片描述

數據處理函數的返回值
在這里插入圖片描述

因為可能收到的數據不滿足一個報文,所以這個函數里多條判斷,報文解析不成功都會返回空,調用得到的字符串內容為空時跳出繼續讀取。解析成功后返回結果將內容發送給客戶端
在這里插入圖片描述
解析報文時有多條判斷,先找\n,找到說明有數據的長度,然后根據長度獲取內容,檢查數據長度和計算的長度符不符合。不滿足上面條件的不予處理,否則說明數據長度符合,移除解析了的內容

server.hpp

#pragma once
#include <string>
#include <signal.h>
#include <functional>
#include "log.hpp"
#include "Socket.hpp"using namespace std;
using func_t = std::function<std::string(std::string &package)>;
class server
{
public:server(uint16_t port, func_t fun):_port(port), _fun(fun){}void init(){//創建套接字_listensocket.Socket();_listensocket.Bind(_port);_listensocket.Listen();lg.logmessage(info, "init server done");}void start(){signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);int cnt = 1;while (true){string ip;uint16_t port;int sockfd = _listensocket.Accept(&ip, &port);if (sockfd < 0){continue;}lg.logmessage(info, "get a new link %d", sockfd);if (fork() == 0){_listensocket.Close();string inbuff_stream;// 提供服務while (true){char buff[1280];ssize_t n = read(sockfd, buff, sizeof(buff));if (n > 0){buff[n] = 0;lg.logmessage(debug, "\n%s", buff);inbuff_stream += buff;while (true){string echo = _fun(inbuff_stream);if (echo.empty()){break;}lg.logmessage(debug, "緩沖區\n%s", inbuff_stream.c_str());lg.logmessage(debug, "結果\n%s", echo.c_str());cout << "次數:" << cnt++ << endl;write(sockfd, echo.c_str(), echo.size());}}else if (n == 0){break;}else{break;}}exit(0);}close(sockfd);}}~server(){}
private:Sock _listensocket;uint16_t _port;func_t _fun;
};

server.cc

#include <unistd.h>
#include "server.hpp"
#include "servercal.hpp"
//#include "protocol.hpp"int main()
{ServerCal cal;uint16_t port = 8000;server *tsvp = new server(port, std::bind(&ServerCal::Calcluator, &cal, std::placeholders::_1));tsvp->init();daemon(0, 0);tsvp->start();return 0;
}

std::bind綁定函數和第一個參數

9. 客戶端

客戶端創建連接,成功生成5次隨機的數字和運算符,賦值給請求類,封裝后發送,收到內容后用結果類解析
在這里插入圖片描述
client.cc

#include <time.h>
#include <unistd.h>
#include <assert.h>
#include "Socket.hpp"
#include "protocol.hpp"int main()
{srand(time(NULL));uint16_t port = 8000;string ip = "106.54.46.147";struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_port = htons(port);const string opers = "+-*/%";Sock socket;socket.Socket();bool r = socket.Connect(ip, port);if (!r)return 1;int cnt = 1;while (cnt <= 5){cout << "=============第" << cnt << "次測試...." << "============" << endl;string package;int x = rand() % 100;int y = rand() % 100 + 1;char op = opers[rand() % opers.size()];Request req(x, y, op);req.debugprint();req.serialize(&package);package = encode(package);write(socket._sockfd, package.c_str(), package.size());char buff[1024];int n = read(socket._sockfd, buff, sizeof(buff));string inbuff_stream;if (n > 0){buff[n] = 0;inbuff_stream += buff;string content;bool r = decode(inbuff_stream, &content);assert(r);Response resp;r = resp.deserialize(content);assert(r);resp.debugprint();}cout << "=======================================" << endl;sleep(1);cnt++;}
}

日志

#pragma once
#include <stdarg.h>
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <time.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>using namespace std;#define info 0
#define debug 1
#define warning 2
#define ERROR 3
#define fatal 4#define screen 1
#define onefile 2
#define classfile 3#define path "log.txt"class Log
{
public:Log(int style = screen){printstyle = style;dir = "log/";}void enable(int method){printstyle = method;}const char *leveltostring(int level){switch (level){case 0:return "info";break;case 1:return "debug";break;case 2:return "warning";break;case 3:return "error";break;case 4:return "fatal";break;default:return "none";break;}}void printlog(int level, const string &logtxt){switch (printstyle){case screen:cout << logtxt;break;case onefile:printonefile(path, logtxt);break;case classfile:printclassfile(level, logtxt);break;}}void logmessage(int level, const char *format, ...){time_t t = time(0);tm *ctime = localtime(&t);char leftbuff[1024];sprintf(leftbuff, "[%s]%d-%d-%d %d:%d:%d:", leveltostring(level), ctime->tm_year + 1900,ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);char rightbuff[1024];va_list s;va_start(s, format);vsprintf(rightbuff, format, s);va_end(s);char logtext[2048];sprintf(logtext, "%s %s\n", leftbuff, rightbuff);//printf(logtext);printlog(level, logtext);}void printonefile(const string& logname, const string& logtxt){int fd = open(logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);  if (fd < 0){return;}write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printclassfile(int level, const string &logtxt){//log.txt.infostring filename = dir + path;filename += ".";filename += leveltostring(level);printonefile(filename, logtxt);}~Log(){};private:int printstyle;string dir; //分類日志,放入目錄中
};// int sum(int n, ...)
// {
//     int sum = 0;
//     va_list s;
//     va_start(s, n);//     while (n)
//     {
//         sum = sum + va_arg(s, int);
//         n--;
//     }//     return sum;
// }

10. 結果示例

在這里插入圖片描述
可以加入守護進程,在初始化服務器后開啟守護
在這里插入圖片描述

11. json序列化

上面的序列化和反序列化功能都是字符串處理,比較麻煩。有一種數據交換格式json,有序列化和反序列化的功能,可以用json代替。用條件編譯試試json發送數據

如果沒有先安裝

sudo yum install -y jsoncpp-devel

序列化
先創建json里的通用變量,Json::Value,以鍵值對的方式賦值,key和value
在這里插入圖片描述
再用一個通用變量嵌套一個value類
在這里插入圖片描述
調用序列化功能生成字符串打印,有兩種風格,style內容換行易讀在這里插入圖片描述
反序列化
創建value變量,創建read對象,調用parse功能解析內容
在這里插入圖片描述
利用key-value格式提取內容,嵌套類型定義value變量獲取,再提取一次
在這里插入圖片描述
輸出結果
在這里插入圖片描述

修改protocol文件
用#ifdef #else #endif的格式條件編譯,json方式寫在else的情況里

Request

		Json::Value root;root["x"] = _num1;root["y"] = _num2;root["op"] = _op;Json::FastWriter w;*out = w.write(root);return true;
		Json::Value root;Json::Reader r;r.parse(in, root);_num1 = root["x"].asInt();_num2 = root["y"].asInt();_op = root["op"].asInt();return true;

Response

		Json::Value root;root["res"] = _result;root["code"] = _code;Json::FastWriter w;*out = w.write(root);return true;
        Json::Value root;Json::Reader r;r.parse(in, root);_result = root["res"].asInt();_code = root["code"].asInt();return true;

示例
在這里插入圖片描述
序列化的工具還有protobuf,二進制,更注重效率,可讀性不如json,適用于內部使用

12. 添加條件選項

在這里插入圖片描述
$號可以引入定義的常量,#會注釋后面的內容

13. 再看七層模型

在這里插入圖片描述
會話層的維護是通過server創建子進程提供服務的,接到鏈接就創建一個會話
表示層就是上面定義的協議,就是結構體字符串等固有的數據格式,網絡標準格式就是序列化添加報頭這些動作后的數據
應用層就是處理數據的計算器功能
所以表示層和會話層應用層很難在os實現,根據不同的場景有不同的格式和功能,內容也會有文字聲音圖像等都有可能,無法在os全部實現

如果客戶端連上一直不發數據,會占用資源,可以對時間進行判斷,超時直接掛掉

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

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

相關文章

CSRF跨站請求偽造實戰

目錄 一、定義 二、與XSS的區別 三、攻擊要點 四、實戰 一、定義 CSRF (Cross-site request forgery&#xff0c;跨站請求偽造)&#xff0c;攻擊者利用服務器對用戶的信任&#xff0c;從而欺騙受害者去服務器上執行受害者不知情的請求。在CSRF的攻擊場景中&#xff0c;攻擊…

Django模板層——模板引擎配置

作為Web 框架&#xff0c;Django 需要一種很便利的方法以動態地生成HTML。最常見的做法是使用模板。 模板包含所需HTML 輸出的靜態部分&#xff0c;以及一些特殊的語法&#xff0c;描述如何將動態內容插入。 模板引擎配置 模板引擎使用該TEMPLATES設置進行配置。這是一個配置列…

C++數據結構——哈希桶HashBucket

目錄 一、前言 1.1 閉散列 1.2 開散列 1.3 string 與 非 string 二、哈希桶的構成 2.1 哈希桶的節點 2.2 哈希桶類 三、 Insert 函數 3.1 無需擴容時 3.2 擴容 復用 Insert&#xff1a; 逐個插入&#xff1a; 優缺點比對&#xff1a; 第一種寫法優點 第一種寫法…

gfast:基于全新Go Frame 2.3+Vue3+Element Plus構建的全棧前后端分離管理系統

gfast&#xff1a;基于全新Go Frame 2.3Vue3Element Plus構建的全棧前后端分離管理系統 隨著信息技術的飛速發展和數字化轉型的深入&#xff0c;后臺管理系統在企業信息化建設中扮演著越來越重要的角色。為了滿足市場對于高效、靈活、安全后臺管理系統的需求&#xff0c;gfast應…

OpenUI 可視化 AI:打造令人驚艷的前端設計!

https://openui.fly.dev/ai/new 可視化UI的新時代&#xff1a;通過人工智能生成前端代碼 許久未更新, 前端時間在逛github&#xff0c;發現一個挺有的意思項目&#xff0c;通過口語化方式生成前端UI頁面&#xff0c;能夠直觀的看到效果&#xff0c;下面來給大家演示下 在現代…

SAP FS00如何導出會計總賬科目表

輸入T-code : S_ALR_87012333 根據‘FS00’中找到的總賬科目&#xff0c;進行篩選執行 點擊左上角的列表菜單&#xff0c;選擇‘電子表格’導出即可

echarts-地圖

使用地圖的三種的方式&#xff1a; 注冊地圖(用json或svg,注冊為地圖)&#xff0c;然后使用map地圖使用geo坐標系&#xff0c;地圖注冊后不是直接使用&#xff0c;而是注冊為坐標系。直接使用百度地圖、高德地圖&#xff0c;使用百度地圖或高德地圖作為坐標系。 用json或svg注…

C++中string類的初步介紹

C語言中的字符串 在C語言中&#xff0c;字符串是以\0結尾的一些字符的集合&#xff0c;C標準庫中提供了一系列str系列的庫函數&#xff0c;但這些庫函數與字符串是分離的&#xff0c;不符合面向對象的編程思想。 string類的大致介紹 1.string是表示字符串的字符串類 2.stri…

GpuMall智算云:meta-llama/llama3/Llama3-8B-Instruct-WebUI

LLaMA 模型的第三代&#xff0c;是 LLaMA 2 的一個更大和更強的版本。LLaMA 3 擁有 35 億個參數&#xff0c;訓練在更大的文本數據集上GpuMall智算云 | 省錢、好用、彈性。租GPU就上GpuMall,面向AI開發者的GPU云平臺 Llama 3 的推出標志著 Meta 基于 Llama 2 架構推出了四個新…

pycharm畫圖貓和老鼠

在PyCharm中&#xff0c;你可以使用turtle模塊來畫圖。以下是一個簡單的例子&#xff0c;展示如何使用turtle模塊來繪制一個貓和一個老鼠。 import turtle # 設置窗口標題 turtle.title("畫圖貓和老鼠") # 創建兩個turtle對象&#xff0c;一個用于繪制貓&#xf…

AWS聯網和內容分發之API Gateway

Amazon API Gateway是一種完全托管的服務&#xff0c;可以幫助開發人員輕松創建、發布、維護、監控和保護任意規模的API。API充當應用程序的前門&#xff0c;可從您的后端服務訪問數據、業務邏輯或功能。使用API Gateway&#xff0c;您可以創建RESTful API和WebSocket API&…

lightGBM 集成學習模型 - 以銀行風控業務為例

LightGBM&#xff08;Light Gradient Boosting Machine&#xff09;是基于梯度提升決策樹&#xff08;GBDT&#xff09;的一種改進實現。其核心思想是通過加法模型&#xff08;additive model&#xff09;和前向分布算法&#xff08;forward distribution algorithm&#xff09…

Qt pro工程文件編寫匯總(區分debug和release、32位和64位的方法,編譯輸出目錄等)

前言&#xff1a; 從事qt開發已經好幾年了&#xff0c;但有關pro編寫的一些細節問題一直沒有一個很好的梳理匯總——因為實際工作開發中&#xff0c;往往只需要編譯特定版本的軟件&#xff08;例如32位release版本&#xff09;&#xff0c;項目創建好后并設置好編譯路徑&#x…

ML307R OpenCPU GPIO使用

一、GPIO使用流程圖 二、函數介紹 三、GPIO 點亮LED 四、代碼下載地址 一、GPIO使用流程圖 這個圖是官網找到的&#xff0c;ML307R GPIO引腳電平默認為1.8V&#xff0c;需注意和外部電路的電平匹配&#xff0c;具體可參考《ML307R_硬件設計手冊_OpenCPU版本適用.pdf》中的描…

零基礎PHP入門(一)選擇IDE和配置環境

配置環境 官網下載安裝包&#xff0c;windows https://windows.php.net/download#php-8.3 我是下載的最新版&#xff0c;也可以切換其他版本 https://windows.php.net/downloads/releases/archives/ 下載好壓縮文件后&#xff0c;雙擊解壓到一個目錄 D:\soft\php 復制ph…

成都愛爾眼科醫院《中、歐國際近視手術大數據白皮書2.0》解讀會圓滿舉行

2024年5月12日&#xff0c;愛爾眼科聯合中國健康促進基金會健康傳播與促進專項基金、新華社新媒體中心與中南大學愛爾眼科研究院、愛爾數字眼科研究所重磅發布《中、歐國際近視手術大數據白皮書2.0》。這是繼2021、2022年在國內相繼發布《國人近視手術白皮書》、《2022中、歐近…

Ubuntu系統初始化相關配置

目錄 Ubuntu文件傳輸: ubuntu怎么打開word:安裝wps(應用中心搜索) Ubuntu安裝annoconda

模型蒸餾筆記

文章目錄 一、什么是模型蒸餾二、如何蒸餾三、實踐四、參考文獻 一、什么是模型蒸餾 Hinton在NIPS2014提出了知識蒸餾&#xff08;Knowledge Distillation&#xff09;的概念&#xff0c;旨在把一個大模型或者多個模型ensemble學到的知識遷移到另一個輕量級單模型上&#xff0…

【SpringBoot】SpringBoot中防止接口重復提交(單機環境和分布式環境)

&#x1f4dd;個人主頁&#xff1a;哈__ 期待您的關注 目錄 &#x1f33c;前言 &#x1f512;單機環境下防止接口重復提交 &#x1f4d5;導入依賴 &#x1f4c2;項目結構 &#x1f680;創建自定義注解 ?創建AOP切面 &#x1f697;創建Conotroller &#x1f4bb;分布…

構建高效的在線培訓機構CRM應用架構實踐

在當今數字化時代&#xff0c;在線培訓已成為教育行業的重要趨勢之一。為了提供更好的學習體驗和管理服務&#xff0c;在線培訓機構需要構建高效的CRM&#xff08;Customer Relationship Management&#xff09;應用架構。本文將探討在線培訓機構CRM應用架構的設計與實踐。 一、…