[置顶] TLV----Demo讲解

接触过网络协议的人对TLV一定或多或少的知道.作为一种自定义应用层标准.

TLV使用十分广泛.他对数据封包有着很好的定义,简单实用.



TLV即Type-Length-Value.即我们每个封装成TLV包的数据都必须为其添加Type和Length字段


TLV示意图如下:

[置顶] TLV----Demo讲解_第1张图片



大家首先要区分数据包和数据报.本文的实例仅仅针对TLV数据包.

而并未添加注册一些控制信令和报头形成数据报,而其中实际的数据

有TLV包组成.


TLV数据包和数据报的关系可由下图表示:


[置顶] TLV----Demo讲解_第2张图片



此外,TLV本身有两种结构,一种是基本TLV结构,另外一种是嵌套TLV结构.而本文使用的是嵌套TLV结构.

基本TLV包:

[置顶] TLV----Demo讲解_第3张图片

嵌套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代码如下:

#include <stdio.h>
#include <WinSock2.h>
#include <string>

#pragma comment(lib, "WS2_32")


//定义枚举类型常量,来填充Tpye字段,其中emTlvNRoot填充根TLV包的Type
//emTlvName字段用于填充子TLV字段中名字的Type字段.emTlvAge,emTlvColor类似
//此类型字段是为了TLV包解码时识别到底是哪个TLV包.进而解析出对应的数据.
enum emTLVNodeType
{
	emTlvNNone = 0,
	emTlvNRoot,			//根节点
	emTlvName,			//名字
	emTlvAge,			//年龄
	emTlvColor			//颜色 1 白色 2 黑色
};



//定义要封装成TLV包的数据,包括名字,年龄,颜色。
typedef struct _CAT_INFO
{
	char szName[12];
	int	iAge;
	int iColor;
}CAT_INFO,*LPCAT_INFO;


//此类为TLC类,其中有四个成员函数,WriteInt和Write是用于
//把原始数据封装为TLV包然后存入内存区块.即TLV包编码过程
//而ReadInt和Read用于把内存区块的TLV包解析出来.即为TLV包的解码过程
class CTlvPacket
{

public:

	CTlvPacket(char *pBuf,unsigned int len):
	  m_pData(pBuf),m_uiLength(len),m_pEndData(m_pData+len),
		  m_pWritePtr(m_pData),m_pReadPtr(m_pData) { }

	~CTlvPacket() { }


	bool WriteInt(int data,bool bMovePtr = true)
	{
		int tmp = htonl(data);
		return Write(&tmp,sizeof(int));
	}


	bool Write(const void *pDst,unsigned int uiCount)
	{
		::memcpy(m_pWritePtr,pDst,uiCount);
		m_pWritePtr += uiCount;
		return m_pWritePtr < m_pEndData ? true : false;
	}


	bool ReadInt(int *data,bool bMovePtr = true)
	{
		Read(data,sizeof(int));
		*data = ntohl(*data);
		return true;
	}


	bool Read(void *pDst,unsigned int uiCount)
	{
		::memcpy(pDst,m_pReadPtr,uiCount);
		m_pReadPtr += uiCount;
		return m_pReadPtr < m_pEndData ? true : false;
	}


private:

	char *m_pData;

	unsigned int m_uiLength;

	char *m_pEndData;

	char *m_pWritePtr;

	char *m_pReadPtr;

};

/*

格式:
	root L1 V
		T L V T L V T L V

	L1 的长度即为“T L V T L V T L V”的长度

*/


//此函数实现TLV编码过程
int TLV_EncodeCat(LPCAT_INFO pCatInfo, char *pBuf, int &iLen)
{

	if (!pCatInfo || !pBuf)
	{
		return -1;
	}


	CTlvPacket enc(pBuf,iLen);
	enc.WriteInt(emTlvNRoot);
	enc.WriteInt(20+12+12); //length

	enc.WriteInt(emTlvName);
	enc.WriteInt(12);
	enc.Write(pCatInfo->szName,12);

	enc.WriteInt(emTlvAge);
	enc.WriteInt(4);
	enc.WriteInt(pCatInfo->iAge);

	enc.WriteInt(emTlvColor);
	enc.WriteInt(4);
	enc.WriteInt(pCatInfo->iColor);


	iLen = 8+20+12+12;

	return 0;

}

//此函数实现TLV解码过程
int TLV_DecodeCat(char *pBuf, int iLen, LPCAT_INFO pCatInfo)
{

	if (!pCatInfo || !pBuf)
	{
		return -1;
	}


	CTlvPacket encDec(pBuf,iLen);
	int iType;
	int iSum,iLength;


	encDec.ReadInt(&iType);
	if (emTlvNRoot != iType)
	{
		return -2;
	}
	encDec.ReadInt(&iSum);


	//通过判断Type头字段对TLV包进行解析
	while (iSum > 0)
	{

		encDec.ReadInt(&iType);//读取主TLV包的type头
		encDec.ReadInt(&iLength);//读取主TLV包的length头

		switch(iType)		//此时buff指针移动到子TLV包.并解析子TLV的type头字段
		{

		case emTlvName:
			encDec.Read(pCatInfo->szName,12);
			iSum -= 20;
			break;

		case emTlvAge:
			encDec.ReadInt(&pCatInfo->iAge);
			iSum -= 12;
		    break;

		case emTlvColor:
			encDec.ReadInt(&pCatInfo->iColor);
			iSum -= 12;
			break;

		default:
			printf("TLV_DecodeCat unkonwn error. \n");
		    break;

		}

	}

	return 0;

}

//主函数
int main(int argc, char* argv[])
{

	int iRet, iLen;
	char buf[256] = {0};


	CAT_INFO cat;  //cat为定义的原始数据包括name,age,color
	memset(&cat,0,sizeof(cat));//cat结构体初始化

	//对cat对象赋值
	strcpy(cat.szName,"Tom");
	cat.iAge = 5;
	cat.iColor = 2;

	//实现对cat对象的编码,编码结果存储在buf中.
	iRet = TLV_EncodeCat(&cat,buf,iLen);


	//TLV编码成功与否的判断
	if ( 0 == iRet )
	{
		printf("TLV_EncodeCat ok, iLen = %d. \n",iLen);
	}
	else
	{
		printf("TLV_EncodeCat error \n");
	}

	//将cat结构置为0
	memset(&cat,0,sizeof(cat));

   //TLV包解码过程,将解包后的数据存入cat结构体对象
	iRet = TLV_DecodeCat(buf,iLen,&cat);


	//输出解包后的结构体数据
	if ( 0 == iRet )
	{
		printf("TLV_DecodeCat ok, cat name = %s, age = %d, color = %d. \n",cat.szName,cat.iAge,cat.iColor);
	}
	else
	{
		printf("TLV_DecodeCat error, code = %d. \n", iRet);
	}


	int iWait = getchar();

	return 0;
}



运行结果截图如下:




这里要对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



转载请注明作者:小刘






你可能感兴趣的:(协议,tlv,应用层协议)