用C語言做物聯(lián)網(wǎng)網(wǎng)關(guān)開發(fā)時,經(jīng)常需要通過串口、485接口等從一些傳感器讀取數(shù)據(jù),由于網(wǎng)關(guān)設(shè)備和傳感器所處的環(huán)境復雜多樣,電磁干擾等常常會破壞傳輸?shù)臄?shù)據(jù),為了確保傳輸數(shù)據(jù)的可靠性,通常會采取一些策略,常用的策略:數(shù)據(jù)校驗+超時重傳,具體過程如下:
- 發(fā)送方在發(fā)送數(shù)據(jù)時,在元數(shù)據(jù)的基礎(chǔ)上增加校驗數(shù)據(jù)形成請求數(shù)據(jù)包(data pack),然后將請求數(shù)據(jù)包發(fā)送出去,并啟動守衛(wèi)計時器(Guard Timer)。
- 接收方在收到請求數(shù)據(jù)包后,以跟發(fā)送方相同的算法對元數(shù)據(jù)進行計算得到校驗數(shù)據(jù),然后跟收到的校驗數(shù)據(jù)進行比較,若相同,則說明數(shù)據(jù)可靠,可以使用;反之,說明數(shù)據(jù)被破壞,直接丟棄掉。若數(shù)據(jù)可靠,接收方以同樣的格式做成響應數(shù)據(jù)包回復給發(fā)送方;若發(fā)現(xiàn)數(shù)據(jù)被破壞,接收方不回復任何數(shù)據(jù)。
- 如果發(fā)送方在守衛(wèi)計時器超時前正確地接收到了接收方回復的響應數(shù)據(jù)包,則停止守衛(wèi)計時器,并進行后續(xù)的處理;若發(fā)送方在守衛(wèi)計時器超時時仍未收到接收方回復的響應數(shù)據(jù)包,則重新發(fā)送"1."中的數(shù)據(jù)包,如此反復,直到正確發(fā)送或達到了重傳次數(shù)。
步驟1~3重點為了講述如何確保數(shù)據(jù)可靠傳輸,真實情景中為了能正確地斷包(識別數(shù)據(jù)包),通常會按照如下結(jié)構(gòu)來定義數(shù)據(jù)包:

- 包起始標識:通常使用固定數(shù)據(jù),比如0xFA, 0xAA, 0xA5等;
- 元數(shù)據(jù)長度:根據(jù)元數(shù)據(jù)長度進行斷包。比如:元數(shù)據(jù)長度占1字節(jié),校驗數(shù)據(jù)占2字節(jié),當元數(shù)據(jù)長度取值為10時,則數(shù)據(jù)包大小為14字節(jié);
- 元數(shù)據(jù):發(fā)送的或接收的,有一定格式或意義的應用數(shù)據(jù);
- 校驗數(shù)據(jù):按照某種校驗算法(比如checksum,crc等)計算得到的值。
#define MAX_DATA_LEN 100 /* 假設(shè)元數(shù)據(jù)的最大長度為100字節(jié) */
typedef struct tag_data_pack {
uint8_t sop; /* 包起始標識,(Start Of Pack) */
uint8_t len; /* 元數(shù)據(jù)長度,假設(shè)為1字節(jié) */
uint8_t datas[MAX_DATA_LEN + 2]; /* 元數(shù)據(jù) + 2字節(jié)的校驗數(shù)據(jù) */
}data_pack_t;
總線式的通信中,通常還會在數(shù)據(jù)包中加入目標設(shè)備的地址,以便于確定數(shù)據(jù)是發(fā)往總線上的哪個設(shè)備,數(shù)據(jù)包的參考結(jié)構(gòu)如下:

用C語言描述如下:
#define MAX_DATA_LEN 100 /* 假設(shè)元數(shù)據(jù)的最大長度為100字節(jié) */
typedef struct tag_data_pack {
uint8_t sop; /* 包起始標識,(Start Of Pack) */
uint8_t addr; /* 目標設(shè)備地址,假設(shè)為1字節(jié) */
uint8_t len; /* 元數(shù)據(jù)長度,假設(shè)為1字節(jié) */
uint8_t datas[MAX_DATA_LEN + 2]; /* 元數(shù)據(jù) + 2字節(jié)的校驗數(shù)據(jù) */
}data_pack_t;
文中涉及的概念:
發(fā)送方、接收方、元數(shù)據(jù)、校驗數(shù)據(jù)、請求數(shù)據(jù)包、響應數(shù)據(jù)包、守衛(wèi)計時器。