0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何高效實現(xiàn)自定義的應(yīng)用層協(xié)議

技術(shù)讓夢想更偉大 ? 來源:技術(shù)讓夢想更偉大 ? 2023-08-29 10:15 ? 次閱讀

1簡述

互聯(lián)網(wǎng)上充斥著各種各樣的網(wǎng)絡(luò)服務(wù),在對外提供網(wǎng)絡(luò)服務(wù)時,服務(wù)端和客戶端需要遵循同一套數(shù)據(jù)通訊協(xié)議,才能正常的進行通訊;就好像你跟臺灣人溝通用閩南語,跟廣東人溝通就用粵語一樣。

實現(xiàn)自己的應(yīng)用功能時,已知的知名協(xié)議(http,smtp,ftp等)在安全性、可擴展性等方面不能滿足需求,從而需要設(shè)計并實現(xiàn)自己的應(yīng)用層協(xié)議。

2.協(xié)議分類

2.1按編碼方式

二進制協(xié)議比如網(wǎng)絡(luò)通信運輸層中的tcp協(xié)議。

明文的文本協(xié)議比如應(yīng)用層的http、redis協(xié)議。

混合協(xié)議(二進制+明文)比如蘋果公司早期的APNs推送協(xié)議。

2.2按協(xié)議邊界

固定邊界協(xié)議能夠明確得知一個協(xié)議報文的長度,這樣的協(xié)議易于解析,比如tcp協(xié)議。

模糊邊界協(xié)議無法明確得知一個協(xié)議報文的長度,這樣的協(xié)議解析較為復(fù)雜,通常需要通過某些特定的字節(jié)來界定報文是否結(jié)束,比如http協(xié)議。

3.協(xié)議優(yōu)劣的基本評判標(biāo)準(zhǔn)

高效的快速的打包解包減少對cpu的占用,高數(shù)據(jù)壓縮率降低對網(wǎng)絡(luò)帶寬的占用。

簡單的易于人的理解、程序的解析。

易于擴展的對可預(yù)知的變更,有足夠的彈性用于擴展。

容易兼容的

向前兼容,對于舊協(xié)議發(fā)出的報文,能使用新協(xié)議進行解析,只是新協(xié)議支持的新功能不能使用。

向后兼容,對于新協(xié)議發(fā)出的報文,能使用舊協(xié)議進行解析,只是新協(xié)議支持的新功能不能使用。

4.自定義應(yīng)用層協(xié)議的優(yōu)缺點

4.1優(yōu)點

非知名協(xié)議,數(shù)據(jù)通信更安全,黑客如果要分析協(xié)議的漏洞就必須先破譯你的通訊協(xié)議。

擴展性更好,可以根據(jù)業(yè)務(wù)需求和發(fā)展擴展自己的協(xié)議,而已知的知名協(xié)議不好擴展。

4.2缺點

設(shè)計難度高,協(xié)議需要易擴展,最好能向后向前兼容。

實現(xiàn)繁瑣,需要自己實現(xiàn)序列化和反序列化。

5.動手前的預(yù)備知識

5.1大小端

計算機系統(tǒng)在存儲數(shù)據(jù)時起始地址是高地址還是低地址。

大端從高地址開始存儲。

小端從低地址開始存儲。

圖解

a0e0e82c-45ac-11ee-a2ef-92fbcf53809c.jpg

判斷這里以c/c++語言代碼為例,使用了c語言中聯(lián)合體的特性。



#include #include using namespace std; bool bigCheck() { union Check { char a; uint32_t data; }; Check c; c.data = 1; if (1 == c.a) { return false; } return true; } int main() { if (bigCheck()) { cout << "big" << endl; } else { cout << "small" << endl; } return 0; }

5.2網(wǎng)絡(luò)字節(jié)序

顧名思義就是數(shù)據(jù)在網(wǎng)絡(luò)傳送的字節(jié)流中的起始地址的高低,為了避免在網(wǎng)絡(luò)通信中引入其他復(fù)雜性,網(wǎng)絡(luò)字節(jié)序統(tǒng)一是大端的。

5.3本地字節(jié)序

本地操作系統(tǒng)的大小端,不同操作系統(tǒng)可能采用不同的字節(jié)序。

5.4內(nèi)存對象與布局

任何變量,不管是堆變量還是棧變量都對應(yīng)著操作系統(tǒng)中的一塊內(nèi)存,由于內(nèi)存對齊的要求程序中的變量并不是緊湊存儲的,例如一個c語言的結(jié)構(gòu)體Test在內(nèi)存中的布局可能如下圖所示。

a0ed814a-45ac-11ee-a2ef-92fbcf53809c.jpg



struct Test { char a; char b; int32_t c; };

5.5序列化與反序列化

將計算機語言中的內(nèi)存對象轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)流,例如把c語言中的結(jié)構(gòu)體Test轉(zhuǎn)化成uint8_t data[6]字節(jié)流。

將網(wǎng)絡(luò)字節(jié)流轉(zhuǎn)換為計算機語言中的內(nèi)存對象,例如把uint8_t data[6]字節(jié)流轉(zhuǎn)化成c語言中的結(jié)構(gòu)體Test。

a0f9093e-45ac-11ee-a2ef-92fbcf53809c.jpg

6.一個例子

6.1 協(xié)議設(shè)計

本協(xié)議采用固定邊界+混合編碼策略。

協(xié)議頭8字節(jié)的定長協(xié)議頭。支持版本號,基于魔數(shù)的快速校驗,不同服務(wù)的復(fù)用。定長協(xié)議頭使協(xié)議易于解析且高效。

協(xié)議體變長json作為協(xié)議體。json使用明文文本編碼,可讀性強、易于擴展、前后兼容、通用的編解碼算法。json協(xié)議體為協(xié)議提供了良好的擴展性和兼容性。

協(xié)議可視化圖

a106d1cc-45ac-11ee-a2ef-92fbcf53809c.jpg

6.2 協(xié)議實現(xiàn)

talk is easy,just code it,使用c/c++語言來實現(xiàn)。

6.2.1c/c++語言實現(xiàn)

使用結(jié)構(gòu)體MyProtoHead來存儲協(xié)議頭



/* 協(xié)議頭 */ struct MyProtoHead { uint8_t version; //協(xié)議版本號 uint8_t magic; //協(xié)議魔數(shù) uint16_t server; //協(xié)議復(fù)用的服務(wù)號,標(biāo)識協(xié)議之上的不同服務(wù) uint32_t len; //協(xié)議長度(協(xié)議頭長度+變長json協(xié)議體長度) };

使用開源的Jsoncpp類來存儲協(xié)議體https://sourceforge.net/proje...

協(xié)議消息體



/* 協(xié)議消息體 */ struct MyProtoMsg { MyProtoHead head; //協(xié)議頭 Json::Value body; //協(xié)議體 };

打包類



/* MyProto打包類 */ class MyProtoEnCode { public: //協(xié)議消息體打包函數(shù) uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len); private: //協(xié)議頭打包函數(shù) void headEncode(uint8_t * pData, MyProtoMsg * pMsg); };

解包類



typedef enum MyProtoParserStatus { ON_PARSER_INIT = 0, ON_PARSER_HAED = 1, ON_PARSER_BODY = 2, }MyProtoParserStatus; /* MyProto解包類 */ class MyProtoDeCode { public: void init(); void clear(); bool parser(void * data, size_t len); bool empty(); MyProtoMsg * front(); void pop(); private: bool parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); bool parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); private: MyProtoMsg mCurMsg; //當(dāng)前解析中的協(xié)議消息體 queue mMsgQ; //解析好的協(xié)議消息隊列 vector mCurReserved; //未解析的網(wǎng)絡(luò)字節(jié)流 MyProtoParserStatus mCurParserStatus; //當(dāng)前解析狀態(tài) };

6.2.2打包(序列化)



void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg) { //設(shè)置協(xié)議頭版本號為1 *pData = 1; ++pData; //設(shè)置協(xié)議頭魔數(shù) *pData = MY_PROTO_MAGIC; ++pData; //設(shè)置協(xié)議服務(wù)號,把head.server本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint16_t *)pData = htons(pMsg->head.server); pData += 2; //設(shè)置協(xié)議總長度,把head.len本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint32_t *)pData = htonl(pMsg->head.len); } uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len) { uint8_t * pData = NULL; Json::FastWriter fWriter; //協(xié)議json體序列化 string bodyStr = fWriter.write(pMsg->body); //計算協(xié)議消息序列化后的總長度 len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size(); pMsg->head.len = len; //申請協(xié)議消息序列化需要的空間 pData = new uint8_t[len]; //打包協(xié)議頭 headEncode(pData, pMsg); //打包協(xié)議體 memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size()); return pData; }

6.2.3解包(反序列化)



bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; if (curLen < MY_PROTO_HEAD_SIZE) { parserBreak = true; //終止解析 return true; } uint8_t * pData = *curData; //解析版本號 mCurMsg.head.version = *pData; pData++; //解析魔數(shù) mCurMsg.head.magic = *pData; pData++; //魔數(shù)不一致,則返回解析失敗 if (MY_PROTO_MAGIC != mCurMsg.head.magic) { return false; } //解析服務(wù)號 mCurMsg.head.server = ntohs(*(uint16_t*)pData); pData+=2; //解析協(xié)議消息體的長度 mCurMsg.head.len = ntohl(*(uint32_t*)pData); //異常大包,則返回解析失敗 if (mCurMsg.head.len > MY_PROTO_MAX_SIZE) { return false; } //解析指針向前移動MY_PROTO_HEAD_SIZE字節(jié) (*curData) += MY_PROTO_HEAD_SIZE; curLen -= MY_PROTO_HEAD_SIZE; parserLen += MY_PROTO_HEAD_SIZE; mCurParserStatus = ON_PARSER_HAED; return true; } bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE; if (curLen < jsonSize) { parserBreak = true; //終止解析 return true; } Json::Reader reader; //json解析類 if (!reader.parse((char *)(*curData), (char *)((*curData) + jsonSize), mCurMsg.body, false)) { return false; } //解析指針向前移動jsonSize字節(jié) (*curData) += jsonSize; curLen -= jsonSize; parserLen += jsonSize; mCurParserStatus = ON_PARSER_BODY; return true; } bool MyProtoDeCode::parser(void * data, size_t len) { if (len <= 0) { return false; } uint32_t curLen = 0; uint32_t parserLen = 0; uint8_t * curData = NULL; curData = (uint8_t *)data; //把當(dāng)前要解析的網(wǎng)絡(luò)字節(jié)流寫入未解析完字節(jié)流之后 while (len--) { mCurReserved.push_back(*curData); ++curData; } curLen = mCurReserved.size(); curData = (uint8_t *)&mCurReserved[0]; //只要還有未解析的網(wǎng)絡(luò)字節(jié)流,就持續(xù)解析 while (curLen > 0) { bool parserBreak = false; //解析協(xié)議頭 if (ON_PARSER_INIT == mCurParserStatus || ON_PARSER_BODY == mCurParserStatus) { if (!parserHead(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } //解析完協(xié)議頭,解析協(xié)議體 if (ON_PARSER_HAED == mCurParserStatus) { if (!parserBody(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } if (ON_PARSER_BODY == mCurParserStatus) { //拷貝解析完的消息體放入隊列中 MyProtoMsg * pMsg = NULL; pMsg = new MyProtoMsg; *pMsg = mCurMsg; mMsgQ.push(pMsg); } } if (parserLen > 0) { //刪除已經(jīng)被解析的網(wǎng)絡(luò)字節(jié)流 mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen); } return true; }

7.完整源碼與測試

code is easy,just run it.

7.1源碼



#include #include #include #include #include #include #include #include using namespace std; const uint8_t MY_PROTO_MAGIC = 88; const uint32_t MY_PROTO_MAX_SIZE = 10 * 1024 * 1024; //10M const uint32_t MY_PROTO_HEAD_SIZE = 8; typedef enum MyProtoParserStatus { ON_PARSER_INIT = 0, ON_PARSER_HAED = 1, ON_PARSER_BODY = 2, }MyProtoParserStatus; /* 協(xié)議頭 */ struct MyProtoHead { uint8_t version; //協(xié)議版本號 uint8_t magic; //協(xié)議魔數(shù) uint16_t server; //協(xié)議復(fù)用的服務(wù)號,標(biāo)識協(xié)議之上的不同服務(wù) uint32_t len; //協(xié)議長度(協(xié)議頭長度+變長json協(xié)議體長度) }; /* 協(xié)議消息體 */ struct MyProtoMsg { MyProtoHead head; //協(xié)議頭 Json::Value body; //協(xié)議體 }; void myProtoMsgPrint(MyProtoMsg & msg) { string jsonStr = ""; Json::FastWriter fWriter; jsonStr = fWriter.write(msg.body); printf("Head[version=%d,magic=%d,server=%d,len=%d] " "Body:%s", msg.head.version, msg.head.magic, msg.head.server, msg.head.len, jsonStr.c_str()); } /* MyProto打包類 */ class MyProtoEnCode { public: //協(xié)議消息體打包函數(shù) uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len); private: //協(xié)議頭打包函數(shù) void headEncode(uint8_t * pData, MyProtoMsg * pMsg); }; void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg) { //設(shè)置協(xié)議頭版本號為1 *pData = 1; ++pData; //設(shè)置協(xié)議頭魔數(shù) *pData = MY_PROTO_MAGIC; ++pData; //設(shè)置協(xié)議服務(wù)號,把head.server本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint16_t *)pData = htons(pMsg->head.server); pData += 2; //設(shè)置協(xié)議總長度,把head.len本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint32_t *)pData = htonl(pMsg->head.len); } uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len) { uint8_t * pData = NULL; Json::FastWriter fWriter; //協(xié)議json體序列化 string bodyStr = fWriter.write(pMsg->body); //計算協(xié)議消息序列化后的總長度 len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size(); pMsg->head.len = len; //申請協(xié)議消息序列化需要的空間 pData = new uint8_t[len]; //打包協(xié)議頭 headEncode(pData, pMsg); //打包協(xié)議體 memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size()); return pData; } /* MyProto解包類 */ class MyProtoDeCode { public: void init(); void clear(); bool parser(void * data, size_t len); bool empty(); MyProtoMsg * front(); void pop(); private: bool parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); bool parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); private: MyProtoMsg mCurMsg; //當(dāng)前解析中的協(xié)議消息體 queue mMsgQ; //解析好的協(xié)議消息隊列 vector mCurReserved; //未解析的網(wǎng)絡(luò)字節(jié)流 MyProtoParserStatus mCurParserStatus; //當(dāng)前解析狀態(tài) }; void MyProtoDeCode::init() { mCurParserStatus = ON_PARSER_INIT; } void MyProtoDeCode::clear() { MyProtoMsg * pMsg = NULL; while (!mMsgQ.empty()) { pMsg = mMsgQ.front(); delete pMsg; mMsgQ.pop(); } } bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; if (curLen < MY_PROTO_HEAD_SIZE) { parserBreak = true; //終止解析 return true; } uint8_t * pData = *curData; //解析版本號 mCurMsg.head.version = *pData; pData++; //解析魔數(shù) mCurMsg.head.magic = *pData; pData++; //魔數(shù)不一致,則返回解析失敗 if (MY_PROTO_MAGIC != mCurMsg.head.magic) { return false; } //解析服務(wù)號 mCurMsg.head.server = ntohs(*(uint16_t*)pData); pData+=2; //解析協(xié)議消息體的長度 mCurMsg.head.len = ntohl(*(uint32_t*)pData); //異常大包,則返回解析失敗 if (mCurMsg.head.len > MY_PROTO_MAX_SIZE) { return false; } //解析指針向前移動MY_PROTO_HEAD_SIZE字節(jié) (*curData) += MY_PROTO_HEAD_SIZE; curLen -= MY_PROTO_HEAD_SIZE; parserLen += MY_PROTO_HEAD_SIZE; mCurParserStatus = ON_PARSER_HAED; return true; } bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE; if (curLen < jsonSize) { parserBreak = true; //終止解析 return true; } Json::Reader reader; //json解析類 if (!reader.parse((char *)(*curData), (char *)((*curData) + jsonSize), mCurMsg.body, false)) { return false; } //解析指針向前移動jsonSize字節(jié) (*curData) += jsonSize; curLen -= jsonSize; parserLen += jsonSize; mCurParserStatus = ON_PARSER_BODY; return true; } bool MyProtoDeCode::parser(void * data, size_t len) { if (len <= 0) { return false; } uint32_t curLen = 0; uint32_t parserLen = 0; uint8_t * curData = NULL; curData = (uint8_t *)data; //把當(dāng)前要解析的網(wǎng)絡(luò)字節(jié)流寫入未解析完字節(jié)流之后 while (len--) { mCurReserved.push_back(*curData); ++curData; } curLen = mCurReserved.size(); curData = (uint8_t *)&mCurReserved[0]; //只要還有未解析的網(wǎng)絡(luò)字節(jié)流,就持續(xù)解析 while (curLen > 0) { bool parserBreak = false; //解析協(xié)議頭 if (ON_PARSER_INIT == mCurParserStatus || ON_PARSER_BODY == mCurParserStatus) { if (!parserHead(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } //解析完協(xié)議頭,解析協(xié)議體 if (ON_PARSER_HAED == mCurParserStatus) { if (!parserBody(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } if (ON_PARSER_BODY == mCurParserStatus) { //拷貝解析完的消息體放入隊列中 MyProtoMsg * pMsg = NULL; pMsg = new MyProtoMsg; *pMsg = mCurMsg; mMsgQ.push(pMsg); } } if (parserLen > 0) { //刪除已經(jīng)被解析的網(wǎng)絡(luò)字節(jié)流 mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen); } return true; } bool MyProtoDeCode::empty() { return mMsgQ.empty(); } MyProtoMsg * MyProtoDeCode::front() { MyProtoMsg * pMsg = NULL; pMsg = mMsgQ.front(); return pMsg; } void MyProtoDeCode::pop() { mMsgQ.pop(); } int main() { uint32_t len = 0; uint8_t * pData = NULL; MyProtoMsg msg1; MyProtoMsg msg2; MyProtoDeCode myDecode; MyProtoEnCode myEncode; msg1.head.server = 1; msg1.body["op"] = "set"; msg1.body["key"] = "id"; msg1.body["value"] = "9856"; msg2.head.server = 2; msg2.body["op"] = "get"; msg2.body["key"] = "id"; myDecode.init(); pData = myEncode.encode(&msg1, len); if (!myDecode.parser(pData, len)) { cout << "parser falied!" << endl; } else { cout << "msg1 parser successful!" << endl; } pData = myEncode.encode(&msg2, len); if (!myDecode.parser(pData, len)) { cout << "parser falied!" << endl; } else { cout << "msg2 parser successful!" << endl; } MyProtoMsg * pMsg = NULL; while (!myDecode.empty()) { pMsg = myDecode.front(); myProtoMsgPrint(*pMsg); myDecode.pop(); } return 0; }

7.2運行測試

a11dd354-45ac-11ee-a2ef-92fbcf53809c.png

8.總結(jié)

不到350行的代碼向我們展示了一個自定義的應(yīng)用層協(xié)議該如何實現(xiàn),當(dāng)然這個協(xié)議是不夠完善的,還可以對其完善,比如對協(xié)議體進行加密加強協(xié)議的安全性等。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 互聯(lián)網(wǎng)
    +關(guān)注

    關(guān)注

    54

    文章

    11015

    瀏覽量

    102089
  • HTTP
    +關(guān)注

    關(guān)注

    0

    文章

    478

    瀏覽量

    30770
  • TCP
    TCP
    +關(guān)注

    關(guān)注

    8

    文章

    1324

    瀏覽量

    78759
  • 應(yīng)用層
    +關(guān)注

    關(guān)注

    0

    文章

    46

    瀏覽量

    11462

原文標(biāo)題:如何高效實現(xiàn)自定義的應(yīng)用層協(xié)議

文章出處:【微信號:技術(shù)讓夢想更偉大,微信公眾號:技術(shù)讓夢想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    HarmonyOS開發(fā)實例:【自定義Emitter】

    使用[Emitter]實現(xiàn)事件的訂閱和發(fā)布,使用[自定義彈窗]設(shè)置廣告信息。
    的頭像 發(fā)表于 04-14 11:37 ?883次閱讀
    HarmonyOS開發(fā)實例:【<b class='flag-5'>自定義</b>Emitter】

    基于自定義協(xié)議的網(wǎng)絡(luò)地理信息系統(tǒng)

    探索基于自定義協(xié)議開發(fā)網(wǎng)絡(luò)地理信息系統(tǒng)的方法。自定義一套工作于TCP/IP應(yīng)用層協(xié)議,基于該協(xié)議
    發(fā)表于 04-18 10:03 ?34次下載

    1602自定義字符

    1602液晶能夠顯示自定義字符,能夠根據(jù)讀者的具體情況顯示自定義字符。
    發(fā)表于 01-20 15:43 ?1次下載

    如何在LabVIEW中實現(xiàn)自定義控件

    本文檔的主要內(nèi)容詳細(xì)介紹的是如何在LabVIEW中實現(xiàn)自定義控件。
    發(fā)表于 01-14 17:17 ?48次下載
    如何在LabVIEW中<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>自定義</b>控件

    單片機學(xué)習(xí)筆記————51單片機實現(xiàn)常用的自定義串口通訊協(xié)議

    單片機學(xué)習(xí)筆記————51單片機實現(xiàn)常用的自定義串口通訊協(xié)議
    發(fā)表于 11-23 17:06 ?37次下載
    單片機學(xué)習(xí)筆記————51單片機<b class='flag-5'>實現(xiàn)</b>常用的<b class='flag-5'>自定義</b>串口通訊<b class='flag-5'>協(xié)議</b>

    C#與STM32自定義通信協(xié)議

    C#與STM32自定義通信協(xié)議功能:1.可通過C#上位機對多臺STM32下位機進行控制2.自定義上位機與下位機通信協(xié)議
    發(fā)表于 12-24 18:59 ?37次下載
    C#與STM32<b class='flag-5'>自定義</b>通信<b class='flag-5'>協(xié)議</b>

    基于HAL庫的USB自定義HID設(shè)備實現(xiàn)

    基于HAL庫的USB自定義HID設(shè)備實現(xiàn)基于HAL庫的USB自定義HID設(shè)備實現(xiàn)準(zhǔn)備工作CubeMX配置代碼實現(xiàn)基于HAL庫的USB
    發(fā)表于 12-28 20:04 ?13次下載
    基于HAL庫的USB<b class='flag-5'>自定義</b>HID設(shè)備<b class='flag-5'>實現(xiàn)</b>

    自定義視圖組件教程案例

    自定義組件 1.自定義組件-particles(粒子效果) 2.自定義組件- pulse(脈沖button效果) 3.自定義組件-progress(progress效果) 4.
    發(fā)表于 04-08 10:48 ?14次下載

    深入理解RPC自定義網(wǎng)絡(luò)協(xié)議

    只要涉及到網(wǎng)絡(luò)通信,必然涉及到網(wǎng)絡(luò)協(xié)議,應(yīng)用層也是一樣。在應(yīng)用層最標(biāo)準(zhǔn)和常用的就是HTTP協(xié)議。但在很多性能要求較高的場景各大企業(yè)內(nèi)部也會自定義
    的頭像 發(fā)表于 06-12 15:00 ?2400次閱讀

    ArkUI如何自定義彈窗(eTS)

    自定義彈窗其實也是比較簡單的,通過CustomDialogController類就可以顯示自定義彈窗。
    的頭像 發(fā)表于 08-31 08:24 ?1950次閱讀

    ESP32上的自定義UART協(xié)議開源

    電子發(fā)燒友網(wǎng)站提供《ESP32上的自定義UART協(xié)議開源.zip》資料免費下載
    發(fā)表于 02-13 16:38 ?4次下載
    ESP32上的<b class='flag-5'>自定義</b>UART<b class='flag-5'>協(xié)議</b>開源

    自定義算子開發(fā)

    一個完整的自定義算子應(yīng)用過程包括注冊算子、算子實現(xiàn)、含自定義算子模型轉(zhuǎn)換和運行含自定義op模型四個階段。在大多數(shù)情況下,您的模型應(yīng)該可以通過使用hb_mapper工具完成轉(zhuǎn)換并順利部署
    的頭像 發(fā)表于 04-07 16:11 ?2517次閱讀
    <b class='flag-5'>自定義</b>算子開發(fā)

    labview超快自定義控件制作和普通自定義控件制作

    labview超快自定義控件制作和普通自定義控件制作
    發(fā)表于 08-21 10:32 ?11次下載

    單片機自定義協(xié)議FIFO高效發(fā)送數(shù)據(jù)方法

    單片機自定義協(xié)議FIFO高效發(fā)送數(shù)據(jù)方法
    的頭像 發(fā)表于 09-28 17:32 ?667次閱讀
    單片機<b class='flag-5'>自定義</b><b class='flag-5'>協(xié)議</b>FIFO<b class='flag-5'>高效</b>發(fā)送數(shù)據(jù)方法

    TSMaster 自定義 LIN 調(diào)度表編程指導(dǎo)

    LIN(LocalInterconnectNetwork)協(xié)議調(diào)度表是用于LIN總線通信中的消息調(diào)度的一種機制,我們收到越來越多來自不同用戶希望能夠通過接口實現(xiàn)自定義LIN調(diào)度表的需求。所以在
    的頭像 發(fā)表于 05-11 08:21 ?428次閱讀
    TSMaster <b class='flag-5'>自定義</b> LIN 調(diào)度表編程指導(dǎo)