接触过网络协议的人对TLV一定或多或少的知道.作为一种自定义应用层标准.
TLV使用十分广泛.他对数据封包有着很好的定义,简单实用.
TLV即Type-Length-Value.即我们每个封装成TLV包的数据都必须为其添加Type和Length字段
TLV示意图如下:
大家首先要区分数据包和数据报.本文的实例仅仅针对TLV数据包.
而并未添加注册一些控制信令和报头形成数据报,而其中实际的数据
有TLV包组成.
TLV数据包和数据报的关系可由下图表示:
此外,TLV本身有两种结构,一种是基本TLV结构,另外一种是嵌套TLV结构.而本文使用的是嵌套TLV结构.
基本TLV包:
嵌套TLV包:
本文使用嵌套TLV包.
几点说明:
编码方法:
1. 将类型type用htonl转换为网络字节顺序,指针偏移+4
2. 将长度length用htonl转换为网络字节顺序,指针偏移+4
3. 若值value数据类型为int、char、short,则将其转换为网络字节顺序,指针偏移+4;若值为字符串类型,写进后,指针偏移+length
解码方法:
1. 读取type 用ntohl转换为主机字节序得到类型,指针偏移+4
2. 读取lengh用ntohl转换为主机字节序得到长度;指针偏移+4
3. 根据得到的长度读取value,若value数据类型为int、char、short,用ntohl转换为主机字节序,指针偏移+4;若value数据类型为字符串类型,指针偏移+length
Type和Length的长度固定,一般那是2、4个字节(这里统一采用4个字节);
Value的长度有Length指定
Demo代码如下:
1 #include <stdio.h> 2 #include <WinSock2.h> 3 #include <string> 4 5 #pragma comment(lib, "WS2_32") 6 7 8 //定义枚举类型常量,来填充Tpye字段,其中emTlvNRoot填充根TLV包的Type 9 //emTlvName字段用于填充子TLV字段中名字的Type字段.emTlvAge,emTlvColor类似 10 //此类型字段是为了TLV包解码时识别到底是哪个TLV包.进而解析出对应的数据. 11 enum emTLVNodeType 12 { 13 emTlvNNone = 0, 14 emTlvNRoot, //根节点 15 emTlvName, //名字 16 emTlvAge, //年龄 17 emTlvColor //颜色 1 白色 2 黑色 18 }; 19 20 21 22 //定义要封装成TLV包的数据,包括名字,年龄,颜色。 23 typedef struct _CAT_INFO 24 { 25 char szName[12]; 26 int iAge; 27 int iColor; 28 }CAT_INFO,*LPCAT_INFO; 29 30 31 //此类为TLC类,其中有四个成员函数,WriteInt和Write是用于 32 //把原始数据封装为TLV包然后存入内存区块.即TLV包编码过程 33 //而ReadInt和Read用于把内存区块的TLV包解析出来.即为TLV包的解码过程 34 class CTlvPacket 35 { 36 37 public: 38 39 CTlvPacket(char *pBuf,unsigned int len): 40 m_pData(pBuf),m_uiLength(len),m_pEndData(m_pData+len), 41 m_pWritePtr(m_pData),m_pReadPtr(m_pData) { } 42 43 ~CTlvPacket() { } 44 45 46 bool WriteInt(int data,bool bMovePtr = true) 47 { 48 int tmp = htonl(data); 49 return Write(&tmp,sizeof(int)); 50 } 51 52 53 bool Write(const void *pDst,unsigned int uiCount) 54 { 55 ::memcpy(m_pWritePtr,pDst,uiCount); 56 m_pWritePtr += uiCount; 57 return m_pWritePtr < m_pEndData ? true : false; 58 } 59 60 61 bool ReadInt(int *data,bool bMovePtr = true) 62 { 63 Read(data,sizeof(int)); 64 *data = ntohl(*data); 65 return true; 66 } 67 68 69 bool Read(void *pDst,unsigned int uiCount) 70 { 71 ::memcpy(pDst,m_pReadPtr,uiCount); 72 m_pReadPtr += uiCount; 73 return m_pReadPtr < m_pEndData ? true : false; 74 } 75 76 77 private: 78 79 char *m_pData; 80 81 unsigned int m_uiLength; 82 83 char *m_pEndData; 84 85 char *m_pWritePtr; 86 87 char *m_pReadPtr; 88 89 }; 90 91 /* 92 93 格式: 94 root L1 V 95 T L V T L V T L V 96 97 L1 的长度即为“T L V T L V T L V”的长度 98 99 */ 100 101 102 //此函数实现TLV编码过程 103 int TLV_EncodeCat(LPCAT_INFO pCatInfo, char *pBuf, int &iLen) 104 { 105 106 if (!pCatInfo || !pBuf) 107 { 108 return -1; 109 } 110 111 112 CTlvPacket enc(pBuf,iLen); 113 enc.WriteInt(emTlvNRoot); 114 enc.WriteInt(20+12+12); //length 115 116 enc.WriteInt(emTlvName); 117 enc.WriteInt(12); 118 enc.Write(pCatInfo->szName,12); 119 120 enc.WriteInt(emTlvAge); 121 enc.WriteInt(4); 122 enc.WriteInt(pCatInfo->iAge); 123 124 enc.WriteInt(emTlvColor); 125 enc.WriteInt(4); 126 enc.WriteInt(pCatInfo->iColor); 127 128 129 iLen = 8+20+12+12; 130 131 return 0; 132 133 } 134 135 //此函数实现TLV解码过程 136 int TLV_DecodeCat(char *pBuf, int iLen, LPCAT_INFO pCatInfo) 137 { 138 139 if (!pCatInfo || !pBuf) 140 { 141 return -1; 142 } 143 144 145 CTlvPacket encDec(pBuf,iLen); 146 int iType; 147 int iSum,iLength; 148 149 150 encDec.ReadInt(&iType); 151 if (emTlvNRoot != iType) 152 { 153 return -2; 154 } 155 encDec.ReadInt(&iSum); 156 157 158 //通过判断Type头字段对TLV包进行解析 159 while (iSum > 0) 160 { 161 162 encDec.ReadInt(&iType);//读取主TLV包的type头 163 encDec.ReadInt(&iLength);//读取主TLV包的length头 164 165 switch(iType) //此时buff指针移动到子TLV包.并解析子TLV的type头字段 166 { 167 168 case emTlvName: 169 encDec.Read(pCatInfo->szName,12); 170 iSum -= 20; 171 break; 172 173 case emTlvAge: 174 encDec.ReadInt(&pCatInfo->iAge); 175 iSum -= 12; 176 break; 177 178 case emTlvColor: 179 encDec.ReadInt(&pCatInfo->iColor); 180 iSum -= 12; 181 break; 182 183 default: 184 printf("TLV_DecodeCat unkonwn error. \n"); 185 break; 186 187 } 188 189 } 190 191 return 0; 192 193 } 194 195 //主函数 196 int main(int argc, char* argv[]) 197 { 198 199 int iRet, iLen; 200 char buf[256] = {0}; 201 202 203 CAT_INFO cat; //cat为定义的原始数据包括name,age,color 204 memset(&cat,0,sizeof(cat));//cat结构体初始化 205 206 //对cat对象赋值 207 strcpy(cat.szName,"Tom"); 208 cat.iAge = 5; 209 cat.iColor = 2; 210 211 //实现对cat对象的编码,编码结果存储在buf中. 212 iRet = TLV_EncodeCat(&cat,buf,iLen); 213 214 215 //TLV编码成功与否的判断 216 if ( 0 == iRet ) 217 { 218 printf("TLV_EncodeCat ok, iLen = %d. \n",iLen); 219 } 220 else 221 { 222 printf("TLV_EncodeCat error \n"); 223 } 224 225 //将cat结构置为0 226 memset(&cat,0,sizeof(cat)); 227 228 //TLV包解码过程,将解包后的数据存入cat结构体对象 229 iRet = TLV_DecodeCat(buf,iLen,&cat); 230 231 232 //输出解包后的结构体数据 233 if ( 0 == iRet ) 234 { 235 printf("TLV_DecodeCat ok, cat name = %s, age = %d, color = %d. \n",cat.szName,cat.iAge,cat.iColor); 236 } 237 else 238 { 239 printf("TLV_DecodeCat error, code = %d. \n", iRet); 240 } 241 242 243 int iWait = getchar(); 244 245 return 0; 246 }
运行结果截图如下:
这里要对TLV包长:iLen = 8+20+12+12; 进行说明.
为什么是这样呢.
因为一个完整的TLV包的主包包含Type字段和Length字段,每个字段占用4个字节所以共八个字节.
而主TLV包的Value字段包含三个子TLV包.
第一个子TLV包为name,而char szName[12],加之子包Type和子包Length,所以一共20个字节
同理,对于age子包共计八个字节,color子包共计八个字节.
所以整个TLV包的长度就为8+20+12+12
本文参考自博客:http://blog.csdn.net/chexlong/article/details/6974201