二、RDT幀設(shè)計
為實現(xiàn)數(shù)據(jù)分片重組,需自定義統(tǒng)一的幀格式,確保發(fā)送端和接收端遵循相同的解析規(guī)則。推薦幀結(jié)構(gòu)分為5個部分,兼顧完整性校驗和擴展性:
(一)參考幀結(jié)構(gòu)
// 幀結(jié)構(gòu)總覽(字節(jié)數(shù)固定部分+可變部分)
frmBegin[4] | frmInfo[5] | data[dataSize] | frmEnd[2]
// 各部分說明
1. frmBegin[4]:幀起始標志(固定4字節(jié))
- 作用:標識一幀數(shù)據(jù)的開始,用于同步幀邊界
- 示例:0xAA 0xBB 0xCC 0xDD(可自定義,需兩端一致)
2. frmInfo[5]:幀信息頭(固定5字節(jié),含業(yè)務核心信息)
- 子結(jié)構(gòu):type[1] + dataSize[4]
- type[1]:數(shù)據(jù)類型(1字節(jié),自定義業(yè)務類型,如0x01=控制指令、0x02=文件數(shù)據(jù))
- dataSize[4]:數(shù)據(jù)體長度(4字節(jié),大端/小端需兩端統(tǒng)一,推薦大端)
3. data[dataSize]:業(yè)務數(shù)據(jù)體(可變長度,由dataSize指定)
- 存儲實際需要傳輸?shù)臉I(yè)務數(shù)據(jù)(如指令內(nèi)容、文件分片等)
4. frmEnd[2]:幀結(jié)束標志(固定2字節(jié))
- 作用:標識一幀數(shù)據(jù)的結(jié)束,用于校驗幀完整性
- 示例:0xEE 0xFF(可自定義,需兩端一致)
說明:幀結(jié)構(gòu)的固定部分(frmBegin、frmInfo、frmEnd)總長度為11字節(jié)(也可根據(jù)實際場景自行擴展),可變部分長度由dataSize動態(tài)指定,確保解析時可準確定位各字段邊界。
(二)幀結(jié)構(gòu)自定義說明
- 核心約束:
frmBegin、frmInfo、frmEnd 的長度和格式需在發(fā)送端、接收端完全一致,否則會導致解析失?。?/li> - 擴展建議:若需更多業(yè)務字段(如校驗碼、序列號),可擴展
frmInfo 長度(如改為8字節(jié)),示例:
frmInfo[8] = type[1] + seq[2] + dataSize[4] + crc[1](seq=序列號,crc=數(shù)據(jù)校驗碼); - 注意:幀標志(frmBegin/frmEnd)需避免與業(yè)務數(shù)據(jù)重復,防止誤識別幀邊界。
三、幀讀取方法
接收端需按「幀起始標志 → 幀信息頭 → 業(yè)務數(shù)據(jù) → 幀結(jié)束標志」的順序讀取數(shù)據(jù),通過循環(huán)重試機制處理分片傳輸場景,確保獲取完整幀。
以下為各環(huán)節(jié)的標準實現(xiàn)(C語言示例),依賴 RDT 核心接口 RDT_Read(rdt_channel_id, buf, len, timeout)。
1. 讀取幀起始標志(frmBegin)
讀取固定4字節(jié)的幀起始標志,用于定位幀的開始位置。
// 全局變量:RDT通道ID(需在建立RDT連接后獲取,如IOTC_Connect_RDT返回的通道ID)
int rdt_channel_id = -1;
// 幀起始標志定義(需與發(fā)送端一致)
#define FRAME_BEGIN_SIZE 4
const unsigned char FRAME_BEGIN[] = {0xAA, 0xBB, 0xCC, 0xDD};
/**
* 讀取幀起始標志
* @return 0=讀取成功且標志匹配,負數(shù)=錯誤碼(-1=緩沖區(qū)不足,-2=標志不匹配,其他=RDT_Read錯誤碼)
*/
int readFrameBegin() {
int readSize = 0; // 單次讀取字節(jié)數(shù)
int rest = FRAME_BEGIN_SIZE; // 剩余需讀取的字節(jié)數(shù)
unsigned char buffer[FRAME_BEGIN_SIZE] = {0}; // 接收緩沖區(qū)
// 循環(huán)讀取完整的起始標志(處理分片)
while (rest > 0) {
readSize = RDT_Read(
rdt_channel_id,
buffer + (FRAME_BEGIN_SIZE - rest), // 當前寫入位置
rest,
1000 // 超時時間(ms),可根據(jù)業(yè)務調(diào)整
);
// 處理讀取結(jié)果
if (readSize == RDT_ER_TIMEOUT) {
// 超時:繼續(xù)重試(避免因分片傳輸導致讀取失敗)
continue;
} else if (readSize < 0) {
// 非超時錯誤(如通道關(guān)閉):返回RDT錯誤碼
return readSize;
}
rest -= readSize;
}
// 驗證起始標志是否匹配(防止誤識別)
if (memcmp(buffer, FRAME_BEGIN, FRAME_BEGIN_SIZE) != 0) {
return -2; // 標志不匹配,返回自定義錯誤碼
}
return 0; // 讀取成功且標志有效
}
2. 讀取幀信息頭(frmInfo)
讀取固定5字節(jié)的幀信息頭,解析出數(shù)據(jù)類型(type)和數(shù)據(jù)體長度(dataSize),為后續(xù)數(shù)據(jù)讀取做準備。
// 幀信息頭固定大?。?字節(jié))
#define FRAME_INFO_SIZE 5
/**
* 讀取幀信息頭
* @param frmInfo 接收幀信息的緩沖區(qū)(需提前分配至少FRAME_INFO_SIZE字節(jié))
* @param frmInfoBufferSize 緩沖區(qū)實際大?。ㄓ糜诤戏ㄐ孕r灒?
* @return 0=讀取成功,負數(shù)=錯誤碼(-1=參數(shù)非法,其他=RDT_Read錯誤碼)
*/
int readFrameInfo(char* frmInfo, size_t frmInfoBufferSize) {
// 參數(shù)合法性校驗(避免空指針和緩沖區(qū)溢出)
if (frmInfo == NULL || frmInfoBufferSize < FRAME_INFO_SIZE) {
return -1;
}
int readSize = 0;
int rest = FRAME_INFO_SIZE;
memset(frmInfo, 0, frmInfoBufferSize); // 清空緩沖區(qū)
// 循環(huán)讀取完整的幀信息頭
while (rest > 0) {
readSize = RDT_Read(
rdt_channel_id,
frmInfo + (FRAME_INFO_SIZE - rest),
rest,
1000
);
if (readSize == RDT_ER_TIMEOUT) {
continue;
} else if (readSize < 0) {
return readSize;
}
rest -= readSize;
}
return 0;
}
3. 解析幀信息頭(parseFrameInfo)
從讀取到的 frmInfo 中解析出 type(數(shù)據(jù)類型)和 dataSize(數(shù)據(jù)體長度),需注意字節(jié)序轉(zhuǎn)換(若為大端傳輸)。
#include
// 用于ntohl函數(shù)(網(wǎng)絡字節(jié)序轉(zhuǎn)主機字節(jié)序)
/**
* 解析幀信息頭
* @param frmInfo 已讀取的幀信息頭緩沖區(qū)(長度>=FRAME_INFO_SIZE)
* @param outType 輸出參數(shù):數(shù)據(jù)類型(需提前分配內(nèi)存)
* @param outDataSize 輸出參數(shù):數(shù)據(jù)體長度(需提前分配內(nèi)存)
* @return 0=解析成功,-1=參數(shù)非法
*/
int parseFrameInfo(const char* frmInfo, int* outType, int* outDataSize) {
if (frmInfo == NULL || outType == NULL || outDataSize == NULL) {
return -1;
}
// 解析數(shù)據(jù)類型(1字節(jié),直接讀?。?
*outType = (unsigned char)frmInfo[0];
// 解析數(shù)據(jù)體長度(4字節(jié),假設(shè)發(fā)送端為網(wǎng)絡字節(jié)序,轉(zhuǎn)換為主機字節(jié)序)
uint32_t dataSizeNet = 0;
memcpy(&dataSizeNet, frmInfo + 1, 4); // frmInfo[1]~frmInfo[4]為dataSize
*outDataSize = ntohl(dataSizeNet); // 大端轉(zhuǎn)主機字節(jié)序(若為小端則用ntohs/直接讀取)
return 0;
}4. 讀取業(yè)務數(shù)據(jù)體(data)
根據(jù)解析出的 dataSize 讀取完整的業(yè)務數(shù)據(jù),確保緩沖區(qū)大小足夠容納數(shù)據(jù)體。
/**
* 讀取業(yè)務數(shù)據(jù)體
* @param dataBuf 接收數(shù)據(jù)的緩沖區(qū)(需提前分配>=dataSize字節(jié))
* @param dataBufSize 緩沖區(qū)實際大小(用于合法性校驗)
* @param dataSize 預期讀取的數(shù)據(jù)體長度(從frmInfo解析獲得)
* @return 實際讀取的字節(jié)數(shù)(正常=dataSize),負數(shù)=錯誤碼(-1=參數(shù)非法,其他=RDT_Read錯誤碼)
*/
int readOneFrameData(char* dataBuf, size_t dataBufSize, int dataSize) {
// 參數(shù)合法性校驗(防止緩沖區(qū)溢出和非法參數(shù))
if (dataBuf == NULL || dataBufSize < (size_t)dataSize || dataSize <= 0) {
readSize = RDT_Read(
rdt_channel_id,
dataBuf + (dataSize - rest), // 當前寫入位置
rest,
1000
);
if (readSize == RDT_ER_TIMEOUT) {
continue;
} else if (readSize < 0) {
return readSize;
}
totalRead += readSize;
rest -= readSize;
}
return totalRead;
}
5. 讀取幀結(jié)束標志(frmEnd)
讀取固定2字節(jié)的幀結(jié)束標志,驗證幀的完整性。
// 幀結(jié)束標志定義(需與發(fā)送端一致)
#define FRAME_END_SIZE 2
const unsigned char FRAME_END[] = {0xEE, 0xFF};
/**
* 讀取幀結(jié)束標志
* @return 0=讀取成功且標志匹配,負數(shù)=錯誤碼(-2=標志不匹配,其他=RDT_Read錯誤碼)
*/
int readFrameEnd() {
int readSize = 0;
int rest = FRAME_END_SIZE;
unsigned char buffer[FRAME_END_SIZE] = {0};
// 循環(huán)讀取完整的結(jié)束標志
while (rest > 0) {
readSize = RDT_Read(
rdt_channel_id,
buffer + (FRAME_END_SIZE - rest),
rest,
1000
);
if (readSize == RDT_ER_TIMEOUT) {
continue;
} else if (readSize < 0) {
return readSize;
}
rest -= readSize;
}
// 驗證結(jié)束標志是否匹配
if (memcmp(buffer, FRAME_END, FRAME_END_SIZE) != 0) {
return -2;
}
return 0;
}
四、完整讀取示例
將上述分步讀取方法整合,實現(xiàn)持續(xù)監(jiān)聽并解析RDT數(shù)據(jù)幀的完整流程,包含錯誤處理和異常兼容。
// 業(yè)務數(shù)據(jù)緩沖區(qū)大小(根據(jù)實際最大dataSize調(diào)整,示例:100KB)
#define MAX_DATA_BUFFER_SIZE 100 * 1024
// 業(yè)務數(shù)據(jù)處理函數(shù)(示例:根據(jù)數(shù)據(jù)類型分發(fā)處理)
void handleBusinessData(int dataType, const char* dataBuf, int dataSize) {
switch (dataType) {
case 0x01: // 控制指令類型
printf("收到控制指令,長度:%d字節(jié),內(nèi)容:%s\n", dataSize, dataBuf);
// TODO:執(zhí)行控制指令邏輯
break;
case 0x02: // 文件數(shù)據(jù)類型
printf("收到文件數(shù)據(jù),長度:%d字節(jié)\n", dataSize);
// TODO:寫入文件或拼接文件分片
break;
default:
printf("收到未知類型數(shù)據(jù),類型:%d,長度:%d字節(jié)\n", dataType, dataSize);
break;
}
}
// 主循環(huán):持續(xù)讀取并解析RDT數(shù)據(jù)幀
int rdtFrameReadLoop() {
char frmInfo[FRAME_INFO_SIZE] = {0};
char dataBuffer[MAX_DATA_BUFFER_SIZE] = {0};
int dataType = 0;
int dataSize = 0;
int ret = 0;
// 循環(huán)監(jiān)聽數(shù)據(jù)(直到通道關(guān)閉或主動退出)
while (1) {
// 1. 讀取幀起始標志
ret = readFrameBegin();
if (ret < 0) {
if (ret == -2) {
printf("幀起始標志不匹配,跳過異常數(shù)據(jù)\n");
continue; // 標志不匹配,跳過當前數(shù)據(jù),繼續(xù)下一輪監(jiān)聽
} else {
printf("讀取幀起始標志失敗,錯誤碼:%d\n", ret);
break; // RDT通道錯誤,退出循環(huán)
}
}
// 2. 讀取幀信息頭
ret = readFrameInfo(frmInfo, sizeof(frmInfo));
if (ret < 0) {
printf("讀取幀信息頭失敗,錯誤碼:%d\n", ret);
break;
}
// 3. 解析幀信息頭
ret = parseFrameInfo(frmInfo, &dataType, &dataSize);
if (ret < 0) {
printf("解析幀信息頭失敗\n");
continue;
}
// 4. 校驗數(shù)據(jù)大小合法性(防止緩沖區(qū)溢出)
if (dataSize <= 0="" datasize=""> MAX_DATA_BUFFER_SIZE) {
printf("無效的數(shù)據(jù)大?。?d字節(jié)(超出緩沖區(qū)限制),跳過當前幀\n", dataSize);
// 跳過當前幀的剩余數(shù)據(jù)(避免影響后續(xù)幀解析)
// TODO:可選實現(xiàn):讀取并丟棄當前幀的data和frmEnd
continue;
}
// 5. 讀取業(yè)務數(shù)據(jù)體
ret = readOneFrameData(dataBuffer, sizeof(dataBuffer), dataSize);
if (ret < 0) {
printf("讀取業(yè)務數(shù)據(jù)失敗,錯誤碼:%d\n", ret);
break;
}
if (ret != dataSize) {
printf("數(shù)據(jù)體不完整(預期:%d字節(jié),實際:%d字節(jié)),跳過當前幀\n", dataSize, ret);
continue;
}
// 6. 讀取幀結(jié)束標志
ret = readFrameEnd();
if (ret < 0) {
if (ret == -2) {
printf("幀結(jié)束標志不匹配,跳過當前幀\n");
continue;
} else {
printf("讀取幀結(jié)束標志失敗,錯誤碼:%d\n", ret);
break;
}
}
// 7. 處理完整的業(yè)務數(shù)據(jù)
handleBusinessData(dataType, dataBuffer, dataSize);
// 清空緩沖區(qū)(避免殘留數(shù)據(jù)影響下一輪解析)
memset(dataBuffer, 0, sizeof(dataBuffer));
}
printf("RDT幀讀取循環(huán)退出\n");
return ret;
}