淺談自定義通訊協議——TLV(封裝)

最近在寫一個溫度實時監控的項目,要用到TLV通訊協議,看了很多博客,慢慢的從裏面明白了TLV的實現方式及用c語言實現TLV的編碼,下面我將淺談一下TLV,在講TLV之前我們先講一下什麼是通訊協議。

通訊協議

1、通訊協議

協議可以使雙方不需要了解對方的實現細節的情況下進行通信,因此雙方可以是異構的,server可以是c++,client可以是java,基於相同的協議,我們可以用自己熟識的語言工具來實現。

協議一般由一個或多個消息組成,簡單的來說,消息就像是一個Table,由表頭(消息的字段定義,包括名稱與數據類型)與行(字段值)組成。

2、自定義通訊協議

約定好雙方交換數據的編解碼方式,包括一致的基本數據類型,業務類型,字節序、消息內容等。

什麼是自定義通訊協議TLV

TLV協議是BER編碼的一種,全稱是Tag、length、value。該協議簡單高效,能適用於各種通信場景,且具有良好的可擴展性。TLV協議的基本格式如下:
在這裏插入圖片描述
其中,Tag,是報文的唯一標識;Length,表示Value字段的長度;Value字段的數據是需要傳輸的數據,長度由Length字段表示。

只用TLV可能會出現問題

我們該如何理解TLV那,通信的雙方一定要有一種規約,收到的數據該如何解析,比如我們在應用層A用戶給B用戶發數據:
A------- 數據----------->B
數據在收發時,一定會存在三個非常重要的信息:
1、這是什麼數據? Tag
2、數據有多少個字節? Length
3、數據的內容是什麼? Value
B收到A發來的信息,就開始按照這三個開始解析,是不是A發來的消息。但是隻有TLV封裝的話,A給B發送過程中,會出現許多差錯,比如:

  • 問題1:數據可能重合!如果你設置0x01爲溫度,那麼後面有個0x01可能是表示數據的,而不是溫度!
    這時候我們需要加一個報頭,固定爲0xFD,用來標誌一個報文的開始

  • 問題2:數據可能會跳變!你不能保證數據傳輸過程中數據的跳變,0x01表示溫度,跳變爲0x02就表示另外的東西了!

這時候我們需要在一個字節流末尾加一個CRC校驗,傳輸前算一下字節流的大小,傳輸到另外一個端口後再算一下,對比前後兩個CRC的值,如果相同,表示沒有發生字節跳變,如果不同,那就捨棄!

  • 問題3:報頭可能在TLV中!

    同樣也要加CRC校驗

加工後的TLV:
在這裏插入圖片描述

如果還有不清楚爲什麼要加報文頭和CRC校驗的同學,可以參考這篇博客https://blog.csdn.net/qq_43296898/article/details/88863854

爲接下來我要完成網絡socket實現溫度上報,要用到自定義通訊協議TLV,我講用TLV的格式將“ID/時間/溫度“上報,如“RPI0001/2019-01-05 11:40:30/30.0C”; 根據上面介紹的我將把這個數據封裝成TLV格式上報。這裏面有三種數據要發送,所以就要寫三個TLV報文。

後來因爲加入了重傳機制,所以要加入了ACK的報文,所以在c文件中我寫了四個TLV報文

TLV的封包代碼示例

下面是我用c代碼實現的TLV報文格式的發送信息:

#include <stdio.h>
#include <string.h>
#include "header.h"
#include "tlv.crc-itu-t.h"


#define HEAD  0xFD   //定義一個報文頭
#define BUF_SIZE  128   //定義一個buffer的大小
#define TLV_FIXED_SIZE  5  //固定TLV字節流大小,不包含value的值
#define TLV_MINI_SIZE    (TLV_FIXED_SIZE+1)  //TLV字節流的最小值,value的最小值爲1個字節
 
enum   //使用枚舉  Tag的值會自加
{
	TAG_ACK = 1,
	TAG_SN,    //樹莓派上的id
  	TAG_TEMP,   //溫度
	TAG_TIME,   //時間

 }int pack_ack (char *buf , int size);  //聲明ack封裝函數
int pack_sn  (char *buf , int size);//聲明id封裝函數
int pack_temp (char *buf , int size);//聲明溫度封裝函數
int pack_time  (char *buf , int size);//聲明時間溫度封裝函數

int  main (int argc, char **argv)
{
	char buf[BUF_SIZE];
	int   bytes;  //一個封裝函數的字節流的個數

	bytes =  pack_ack (buf , sizeof(buf));
	dump_buf("ack", buf, bytes);//設置dump_buf函數,把所有的字節流都放到dump_buf裏面
	
	bytes =  pack_sn(buf , sizeof(buf));
	dump_buf("sn", buf, bytes);

	bytes =  pack_temp(buf , sizeof(buf));
	dump_buf("temp", buf, bytes);

	bytes =  pack_time (buf , sizeof(buf));
	dump_buf("time", buf, bytes);
	
	return 0;
}
/*ACK報文
 * H              T             L               Value    CRC1    CRC2
 * 0xFD          0x01         0x01              0x00      0x12    0x34
 * */

int pack_ack(char *buf, int size)
{
    unsigned short crc16=0; //CRC校驗值
    int pack_len = 0;
    int data_len = 0;
    int ofset = 0;//buf的索引位置
    char *val="0";
    
    if(!buf || size < TLV_MINI_SIZE)
    {
        printf("Invalid input argument\n");
        return 0;
    }

    buf[ofset]=HEAD;  //將buf[0]設報文頭0xFD
    ofset +=1;//索引加1

    buf[ofset]=TAG_ACK; //將buf[1]設爲TAG_ACK=0x01
    ofset +=1;

    data_len = strlen(val); //ACK報文value的長度
    pack_len = data_len+TLV_FIXED_SIZE;// 封裝TLV的總長度(length)
    buf[ofset]=pack_len;//將buf[2]設爲TLV的總長度
    ofset  += 1;

    memcpy(&buf[ofset], val, data_len);//把ACK的內容拷貝到buf裏,按字節拷貝
    ofset +=data_len; //索引加到data_len個字節

    crc16 = crc_itu_t(MAGIC_CRC, buf, ofset);//調用crc函數,此函數在crc頭文件中
    ushort_to_bytes(&buf[ofset], crc16);
    ofset +=2;

    return ofset;//返回索引值
}
/*樹莓派的編號ID=RPI0001報文
 * H      T             L               Value    CRC1    CRC2
 * 0xFD  0x01         0x01              0x07      0x12    0x34
 * */

int pack_sn(char *buf, int size)//聲明id封裝函數
{
    unsigned short crc16 = 0;
    char           *id = "RPI0001";
    int     ofset = 0;
    int         pack_len = 0;
    int     data_len = 0;

    if(!buf || size < TLV_MINI_SIZE)
    {
        printf("Invalid input argument \n");
        return 0;
    }

    buf[ofset]=HEAD;
    ofset+=1;

    buf[ofset]=TAG_SN;
    ofset +=1;

    data_len=strlen(id);
    pack_len = data_len + TLV_FIXED_SIZE;
    buf[ofset]=pack_len;
    ofset +=1;

    memcpy(&buf[ofset], id, data_len);
    ofset += data_len;

    crc16=crc_itu_t(MAGIC_CRC, buf, ofset);
    ushort_to_bytes(&buf[ofset], crc16);
    ofset +=2;

    return ofset; //返回索引值
}

/* 樹莓派上獲取溫度30.5C報文
 * H                  T            L              Value           CRC1    CRC2
 * 0xFD              0x03         0x00         0x14 0x3A           0x12    0x34
*/

int  pack_temp(char *buf, int size)
{
    unsigned short crc16=0;
    int     pack_len = 0;
    int     data_len = 0;
    int     ofset = 0;
    float   temp;
    char    datatime[32];
    int     i,j;

    if(!buf || size < TLV_MINI_SIZE)
     {

        printf("Invalid input adgument\n");
        return 0;

    }
    buf[ofset]=HEAD;
    ofset +=1;

    buf[ofset]=TAG_TEMP;
    ofset +=1;

    ds18b20_get_temperature(&temp);
    i=(int)temp; //整數部分
    j=(int) ((temp-i) *100);//小數部分

    data_len = sizeof(temp);
    pack_len= data_len + TLV_FIXED_SIZE;
    buf[ofset]=pack_len;
    ofset +=1;

    buf[ofset]=i;
    ofset+=1;
    buf[ofset]=j;
    ofset+=1;

    crc_itu_t(MAGIC_CRC, buf, ofset);
    ushort_to_bytes(&buf[ofset], crc16);
 ushort_to_bytes(&buf[ofset], crc16);
    ofset +=2;
    return ofset;//返回索引值
}
/* 獲取系統時間的報文 2020-4-12 23:22:00
 * H                  T             L                   Value                                  
CRC1    CRC2
 * 0xFD  0x04         0x06          0x14    0x04 0x0c 0x17 0x16 0x00              0x12    0x34
 * */

int pack_time (char *buf, int size)
{
    unsigned short crc16=0;
    int  ofset = 0;
    int  pack_len = 0;
    int  data_len = 0;
    char  datatime[32];
    char *datime;
    if( !buf || size < TLV_MINI_SIZE)
    {
        printf("Invald input argument\n");
        return 0;
    }

    buf[ofset]=HEAD;
    ofset+=1;

    buf[ofset]=TAG_TIME;
    ofset +=1;
    memset(datatime, 0, sizeof(datatime));
    int time=get_sys_time(datatime);
    data_len = time;
    pack_len =data_len + TLV_MINI_SIZE;
    buf[ofset]=pack_len;
    ofset+=1;

    for(int i=0; i< data_len; i++, ofset++)
        buf[data_len]=datatime[i];

    crc16=crc_itu_t(MAGIC_CRC,buf, ofset);
    ushort_to_bytes(&buf[ofset], crc16);
    ofset +=2;
    return ofset;//返回索引值

}

void dump_buf(char *type, char *data, int len)//data指針,指向buf的首地址,len是buf的長度
{
    if(type)
    {
        printf("%s:\n", type);
    }

    int i;
    for(i=0; i<len; i++)
    {
        printf("0x%02x\n",data[i]);
             printf("0x%02x\n",data[i]);
    }
    printf("\n");

}

後續我將會更新TLV的解包過程!代碼可能存在bug,如果有什麼問題,還請大家指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章