金融系统中PBOC/EMV的TLV的算法实现(含C++/C#)

来源:互联网 发布:淘宝开的店铺怎么关闭 编辑:程序博客网 时间:2024/04/30 03:42

    TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换。在金融系统以及认证中,PBOC以及EMV的认证规范文档上面也有对TLV做了一些说明,由于认证规范都是英文文档,所以有些人可能不易于理解。首先我先介绍下什么是TLV,TLV的用途是什么,以及如何实现它的打包解包算法。

 

      金融系统中的TLV是BER-TLV编码的一个特例编码规范,而BER-TLV是ISO定义中的规范。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度

      其实,在BER编码的方式有两种情况,一种是确定长度的方式,一种是不确定长度的方式,而金融TLV选择了确定长度的方式,这样在设备之间的数据传输量上就可以减少。

 

Tag域说明:

      先来看下TLV的Tag是如何编码的,先看下图:

image

 

这张图说明了Tag域第一个字节的编码格式。其中b8-b1代表1个字节中的8个位。

其中b8,b7代表数据的类别。根据2个位的组合,有四种类别:通用类别,应用类别,上下文语境类别,专用类别。这个主要用于在于终端设备交互的时候,确定数据处理的类型。

b6代表的是数据元结构,也就是说它是属于简单数据元结构,还是属于结构(复合)数据元结构。当b6为1的时候,就需要后续的字节进行扩展。也就是说复合的TLV中,value域里也包含一个或多个TLV,这个稍后接着介绍。

当b5-b1代表串行号,当5个位都为1时,需要将tag域扩展到下一个字节中,也就是Tag占2个字节;而当5个位都不全为1时,该Tag域就只占1个字节。

      现在,看下b5-b1:11111的情况:

image

      从图中我们看到BER-TLV编码中,当b8为1时,Tag还需要有后续字节,直到b8为0为止。从EMV文档中的说明,Tag最多只占用2个字节,所以这样就相对比较简单一些了。当b8为0时,该Tag域结束,总共就占用2个字节。

 

Length域说明:

      在文档中没有图片叙述,我自绘一个:

image

当b8为0时,该字节的b7-b1作为value域的长度;当b8为1时,b7-b1作为后续字节的长度,也就是说,例如有这样一个值:10000011,代表后续还有3个字节作为value域的长度(本字节不算,本字节变为作为一个Length的索引)。3个字节代表value的长度,意味着什么呢,意味着内容的长度当需要很大的时候,字节的位数就会跟着越高,3个字节就代表最大可以有256*256*256的长度。

 

Value域说明:

      也是分成两种情况考虑,就是前面说到的Tag分成两个数据元结构,一种是简单数据元结构,一种是复合数据元架构:

      先来看看简单数据元结构:

      image

Tag就是Tag,没有子标签Tag,基本结构就是T-L-V。

      再看下符合数据元结构:

image

后面的Value说明:Primitive or constructed BER-TLV data object number,包含一个简单数据元结构或者也可以是一个符合数据元结构。这样可以看出,算法中必须要实现Tag的嵌套功能,递归算法不可少。

 

算法实现:

      根据以上的说明现在来实现它的打包解包的算法(打包的目的是将一个从终端上发的请求数据——字节数组,构造成一系列的TLV结构实体;解包的目的刚好相反,就是将TLV结构实体解析成字节数组,然后通过IC卡发送到终端上)。

      首先定义一个TLV结构实体:

// TLV结构体struct TLVEntity {unsigned char* Tag;//标记unsigned char* Length;//数据长度unsigned char* Value;//数据unsigned int TagSize;//标记占用字节数unsigned int LengthSize;//数据长度占用字节数TLVEntity* Sub_TLVEntity;//子嵌套TLV实体};
其中TagSize代表Tag字段的字节长度,LengthSize代表Length的字节长度,这里的Length记住要使用char*,由于前面说过,Length可能包含多个字节,通过多个字节确定Value域的长度,Sub_TLVEntity作为子嵌套的TLV结构体。

      定义一个TLVPackage的打包类:

TLVPackage.h:

// TLV打包类class TLVPackage{public:TLVPackage();virtual ~TLVPackage();//构造TLV实体static void Construct(unsigned char* buffer, unsigned int bufferLength, TLVEntity* tlvEntity, unsigned int& entityLength, unsigned int status=0);//解析TLV字节数组static void Parse(TLVEntity* tlvEntity, unsigned int entityLength, unsigned char* buffer, unsigned int& bufferLength);};

具体方法实现:

// 构造TLVvoid TLVPackage:: Construct(unsigned char* buffer, unsigned int bufferLength, TLVEntity* tlvEntity, unsigned int& entityLength,unsigned int status){int currentTLVIndex = 0;int currentIndex = 0;int currentStatus = 'T'; //状态字符unsigned long valueSize = 0;while(currentIndex < bufferLength){switch(currentStatus){case 'T':valueSize = 0;//判断是否单一结构if((status == 1 && buffer[currentIndex] & 0x20) != 0x20){tlvEntity[currentTLVIndex].Sub_TLVEntity = NULL; //单一结构时将子Tag置空//判断是否多字节Tagif((buffer[currentIndex] & 0x1f) == 0x1f){int endTagIndex = currentIndex;while((buffer[++endTagIndex] & 0x80) == 0x80); //判断第二个字节的最高位是否为1int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节tlvEntity[currentTLVIndex].Tag = new unsigned char[tagSize];memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize); tlvEntity[currentTLVIndex].Tag[tagSize] = 0;tlvEntity[currentTLVIndex].TagSize = tagSize;currentIndex += tagSize;}else{tlvEntity[currentTLVIndex].Tag = new unsigned char[1];memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1);tlvEntity[currentTLVIndex].Tag[1] = 0;tlvEntity[currentTLVIndex].TagSize = 1;currentIndex += 1;}}else{//判断是否多字节Tagif((buffer[currentIndex] & 0x1f) == 0x1f){int endTagIndex = currentIndex;while((buffer[++endTagIndex] & 0x80) == 0x80); //判断第二个字节的最高位是否为1int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节tlvEntity[currentTLVIndex].Tag = new unsigned char[tagSize];memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize); tlvEntity[currentTLVIndex].Tag[tagSize] = 0;tlvEntity[currentTLVIndex].TagSize = tagSize;currentIndex += tagSize;}else{tlvEntity[currentTLVIndex].Tag = new unsigned char[1];memcpy(tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1);tlvEntity[currentTLVIndex].Tag[1] = 0;tlvEntity[currentTLVIndex].TagSize = 1;currentIndex += 1;}//分析SubTagint subLength = 0;unsigned char* temp;if((buffer[currentIndex] & 0x80) == 0x80){for (int index = 0; index < 2; index++){subLength += buffer[currentIndex + 1 + index] << (index * 8); //计算Length域的长度}temp = new unsigned char[subLength];memcpy(temp, buffer + currentIndex + 3, subLength);}else{subLength = buffer[currentIndex];temp = new unsigned char[subLength];memcpy(temp, buffer + currentIndex + 1, subLength);}temp[subLength] = 0;//memcpy(temp, buffer + currentIndex + 1, subLength);unsigned int oLength;tlvEntity[currentTLVIndex].Sub_TLVEntity = new TLVEntity[1];Construct(temp, subLength, tlvEntity[currentTLVIndex].Sub_TLVEntity, oLength);}currentStatus = 'L';break;case 'L'://判断长度字节的最高位是否为1,如果为1,则该字节为长度扩展字节,由下一个字节开始决定长度if((buffer[currentIndex] & 0x80) != 0x80){tlvEntity[currentTLVIndex].Length = new unsigned char[1];memcpy(tlvEntity[currentTLVIndex].Length, buffer + currentIndex, 1);tlvEntity[currentTLVIndex].Length[1] = 0;tlvEntity[currentTLVIndex].LengthSize = 1;valueSize = tlvEntity[currentTLVIndex].Length[0];currentIndex += 1;}else{//为1的情况unsigned int lengthSize = buffer[currentIndex] & 0x7f;currentIndex += 1; //从下一个字节开始算Length域for (int index = 0; index < lengthSize; index++){valueSize += buffer[currentIndex + index] << (index * 8); //计算Length域的长度}tlvEntity[currentTLVIndex].Length = new unsigned char[lengthSize];memcpy(tlvEntity[currentTLVIndex].Length, buffer + currentIndex, lengthSize);tlvEntity[currentTLVIndex].Length[lengthSize] = 0;tlvEntity[currentTLVIndex].LengthSize = lengthSize;currentIndex += lengthSize;}currentStatus = 'V';break;case 'V':tlvEntity[currentTLVIndex].Value = new unsigned char[valueSize];memcpy(tlvEntity[currentTLVIndex].Value, buffer + currentIndex, valueSize);tlvEntity[currentTLVIndex].Value[valueSize] = 0;currentIndex += valueSize;//进入下一个TLV构造循环currentTLVIndex += 1;currentStatus = 'T';break;default:return;}}entityLength = currentTLVIndex;}// 解析TLVvoid TLVPackage::Parse(TLVEntity* tlvEntity, unsigned int entityLength, unsigned char* buffer, unsigned int& bufferLength){int currentIndex = 0;int currentTLVIndex = 0;unsigned long valueSize = 0;while(currentTLVIndex < entityLength){valueSize = 0;TLVEntity entity = tlvEntity[currentTLVIndex];memcpy(buffer + currentIndex, entity.Tag, entity.TagSize);//解析TagcurrentIndex += entity.TagSize;for (int index = 0; index < entity.LengthSize; index++){valueSize += entity.Length[index] << (index * 8); //计算Length域的长度}if(valueSize > 127){buffer[currentIndex] = 0x80 | entity.LengthSize;currentIndex += 1;}memcpy(buffer + currentIndex, entity.Length, entity.LengthSize);//解析LengthcurrentIndex += entity.LengthSize;//判断是否包含子嵌套TLVif(entity.Sub_TLVEntity == NULL){memcpy(buffer + currentIndex, entity.Value, valueSize);//解析ValuecurrentIndex += valueSize;}else{unsigned int oLength;Parse(entity.Sub_TLVEntity, 1, buffer + currentIndex, oLength);//解析子嵌套TLVcurrentIndex += oLength;}currentTLVIndex++;}buffer[currentIndex] = 0;bufferLength = currentIndex;}

然后写测试程序:

// 上发测试数据unsigned char requestBuf[] = {0x9F, 0x1C, 0x12, 0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32, 0x30, 0x34, 0x30, 0x34, 0x32, 0x37, 0x31, 0x38, 0x9F, 0x62, 0x01, 0x01, 0x57, 0x12, 0x62, 0x22, 0x89, 0x00, 0x00, 0x02, 0x91, 0x01, 0xD0, 0x90, 0x32, 0x01, 0x02, 0x47, 0x17, 0x13, 0x00, 0x0F, 0x5F, 0x20, 0x0A, 0x48, 0x55, 0x47, 0x55, 0x4F, 0x20, 0x4D, 0x49, 0x4E, 0x47, 0x9F, 0x1F, 0x3C, 0x25, 0x39, 0x39, 0x36, 0x32, 0x32, 0x32, 0x38, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x39, 0x31, 0x30, 0x31, 0x5E, 0x47, 0x55, 0x4F, 0x20, 0x4D, 0x49, 0x4E, 0x47, 0x2F, 0x48, 0x55, 0x5E, 0x30, 0x39, 0x30, 0x33, 0x32, 0x30, 0x31, 0x30, 0x32, 0x34, 0x37, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0x39, 0x30, 0x30, 0x3F}; TLVEntity tlvEntity[TLV_MAX_LENGTH];unsigned int tlv_count;//构造TLVTLVPackage::Construct(requestBuf, sizeof(requestBuf), tlvEntity, tlv_count);unsigned char parseBuf[1024];unsigned int buf_count;//解析TLVTLVPackage::Parse(tlvEntity, tlv_count, parseBuf, buf_count);if(strncmp((char*)parseBuf, (char*)requestBuf, sizeof(requestBuf)) == 0){AfxMessageBox("TRUE");}else{AfxMessageBox("FALSE");}

最后测试结果中,可以得到最后将弹出“TRUE”的对话框。证明构造TLV得到的TLVEntity,再对这个实体进行解析,可以的到解析后的字节数组,最后通过strncmp的方法比较判断,是否原始字节数组和解析后的字节数组是否一致。

 

另外,为了方便C#程序员的使用,我对该C++程序重新改写了一下,得出的结果也是一样的。

TLVEntity.cs

///     /// TLV实体    ///     public class TLVEntity    {        ///         /// 标记        ///         public byte[] Tag { get; set; }        ///         /// 数据长度        ///         public byte[] Length { get; set; }        ///         /// 数据        ///         public byte[] Value { get; set; }        ///         /// 标记占用字节数        ///         public int TagSize { get; set; }        ///         /// 数据长度占用字节数        ///         public int LengthSize { get; set; }        ///         /// 子嵌套TLV实体        ///         public TLVEntity Sub_TLVEntity { get; set; }    }

以及TLVPackage.cs

///     /// TLV打包类    ///     public class TLVPackage    {        ///         /// 构造TLV        ///         ///         public static List Construct(byte[] buffer)        {            List list = new List();            int currentTLVIndex = 0;            int currentIndex = 0;            int currentStatus = 'T'; //状态字符            int valueSize = 0;            TLVEntity tlvEntity = null;            while (currentIndex < buffer.Length)            {                switch (currentStatus)                {                    case 'T':                        tlvEntity = new TLVEntity();                        valueSize = 0;                        //判断是否单一结构                        if ((buffer[currentIndex] & 0x20) != 0x20)                        {                            tlvEntity.Sub_TLVEntity = null; //单一结构时将子Tag置空【】                            //判断是否多字节Tag                            if ((buffer[currentIndex] & 0x1f) == 0x1f)                            {                                int endTagIndex = currentIndex;                                while ((buffer[++endTagIndex] & 0x80) == 0x80) ; //判断第二个字节的最高位是否为1                                int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节                                tlvEntity.Tag = new byte[tagSize];                                Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, tagSize);                                tlvEntity.TagSize = tagSize;                                currentIndex += tagSize;                            }                            else                            {                                tlvEntity.Tag = new byte[1];                                Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, 1);                                tlvEntity.TagSize = 1;                                currentIndex += 1;                            }                        }                        else                        {                            //判断是否多字节Tag                            if ((buffer[currentIndex] & 0x1f) == 0x1f)                            {                                int endTagIndex = currentIndex;                                while ((buffer[++endTagIndex] & 0x80) == 0x80) ; //判断第二个字节的最高位是否为1                                int tagSize = endTagIndex - currentIndex + 1; //计算Tag包含多少字节                                tlvEntity.Tag = new byte[tagSize];                                Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, tagSize);                                tlvEntity.TagSize = tagSize;                                currentIndex += tagSize;                            }                            else                            {                                tlvEntity.Tag = new byte[1];                                Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, 1);                                tlvEntity.TagSize = 1;                                currentIndex += 1;                            }                            //分析SubTag                            int subLength = 0;                            byte[] temp;                            if ((buffer[currentIndex] & 0x80) == 0x80)                            {                                for (int index = 0; index < 2; index++)                                {                                    subLength += buffer[currentIndex + 1 + index] << (index * 8); //计算Length域的长度                                }                                temp = new byte[subLength];                                Array.Copy(buffer, currentIndex + 3, temp, 0, subLength);                            }                            else                            {                                subLength = buffer[currentIndex];                                temp = new byte[subLength];                                Array.Copy(buffer, currentIndex + 1, temp, 0, subLength);                            }                            int oLength;                            tlvEntity.Sub_TLVEntity = new TLVEntity();                            List tempList = Construct(temp);                            tlvEntity.Sub_TLVEntity = tempList[0];                        }                        currentStatus = 'L';                        break;                    case 'L':                        //判断长度字节的最高位是否为1,如果为1,则该字节为长度扩展字节,由下一个字节开始决定长度                        if ((buffer[currentIndex] & 0x80) != 0x80)                        {                            tlvEntity.Length = new byte[1];                            Array.Copy(buffer, currentIndex, tlvEntity.Length, 0, 1);                            tlvEntity.LengthSize = 1;                            valueSize = tlvEntity.Length[0];                            currentIndex += 1;                        }                        else                        {                            //为1的情况                            int lengthSize = buffer[currentIndex] & 0x7f;                            currentIndex += 1; //从下一个字节开始算Length域                            for (int index = 0; index < lengthSize; index++)                            {                                valueSize += buffer[currentIndex + index] << (index * 8); //计算Length域的长度                            }                            tlvEntity.Length = new byte[lengthSize];                            Array.Copy(buffer, currentIndex, tlvEntity.Length, 0, lengthSize);                            tlvEntity.LengthSize = lengthSize;                            currentIndex += lengthSize;                        }                        currentStatus = 'V';                        break;                    case 'V':                        tlvEntity.Value = new byte[valueSize];                        Array.Copy(buffer, currentIndex, tlvEntity.Value, 0, valueSize);                        currentIndex += valueSize;                        //进入下一个TLV构造循环                        list.Add(tlvEntity);                        currentStatus = 'T';                        break;                    default:                        return new List();                }            }            return list;        }        ///         /// 解析TLV        ///         ///         ///         public static byte[] Parse(List list)        {            byte[] buffer = new byte[4096];            int currentIndex = 0;            int currentTLVIndex = 0;            int valueSize = 0;            while (currentTLVIndex < list.Count())            {                valueSize = 0;                TLVEntity entity = list[currentTLVIndex];                Array.Copy(entity.Tag, 0, buffer, currentIndex, entity.TagSize);//解析Tag                currentIndex += entity.TagSize;                for (int index = 0; index < entity.LengthSize; index++)                {                    valueSize += entity.Length[index] << (index * 8); //计算Length域的长度                }                if (valueSize > 127)                {                    buffer[currentIndex] = Convert.ToByte(0x80 | entity.LengthSize);                    currentIndex += 1;                }                Array.Copy(entity.Length, 0, buffer, currentIndex, entity.LengthSize);//解析Length                currentIndex += entity.LengthSize;                //判断是否包含子嵌套TLV                if (entity.Sub_TLVEntity == null)                {                    Array.Copy(entity.Value, 0, buffer, currentIndex, valueSize); //解析Value                    currentIndex += valueSize;                }                else                {                    byte[] tempBuffer = Parse(new List { entity.Sub_TLVEntity });                    Array.Copy(tempBuffer, 0, buffer, currentIndex, tempBuffer.Length);//解析子嵌套TLV                    currentIndex += tempBuffer.Length;                }                currentTLVIndex++;            }            byte[] resultBuffer = new byte[currentIndex];            Array.Copy(buffer, 0, resultBuffer, 0, currentIndex);            return resultBuffer;        }    }

接着,写测试程序:

static byte[] requestBuffer =         {            0x9F, 0x1C, 0x82, 0x2C, 0x01,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,            0x71, 0x07, 0x9f, 0x19, 0x04, 0x11, 0x22, 0x33, 0x44        };        static void Main(string[] args)        {            Console.WriteLine("待组装的数据包:");            requestBuffer.ToList().ForEach(o => { Console.Write("{0},", "0x" + o.ToString("X")); });            Console.WriteLine("\r\n\r\n开始构造TLV");            //构造TLV            List list = TLVPackage.Construct(requestBuffer);            Console.WriteLine("\r\n构造结束!");            Console.WriteLine("\r\n开始解析TLV");            //解析TLV            byte[] responseBuffer = TLVPackage.Parse(list);            Console.WriteLine("\r\n解析结束!");            Console.WriteLine("\r\n解析结果:");            if (ByteEquals(requestBuffer, responseBuffer))            {                Console.WriteLine("Equal!");            }            else            {                Console.WriteLine("Not Equal!");            }            Console.ReadKey();        }

运行结果:

image

 

总结

TLV在数据通信方面,其实运用得很广泛,在应用层数据通信中,如HTTP协议,HTML,XML语言本身定义了一些标签(Body,Head,Script)对数据串行化,接收方再根据标签解析原始数据,通过浏览器展现出来。因此本质上也是属于TLV协议的设计模式。甚至在传输层的TCP,UDP协议,你完全可以通过TLV实现自定义的应用协议。

 

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 狗狗后腿内八字怎么办 快走后小腿粗了怎么办? 猫的嘴巴烂了怎么办 苹果8丢了已关机怎么办 肚子吃多了难受怎么办 喝水喝的肚子胀怎么办 肚子吃撑了难受怎么办 肚子撑得想吐怎么办 吃饭吃的太饱怎么办 吃饭吃的太撑怎么办 跑步迈不开步子怎么办 踏步走步子反了怎么办 微信不支持计步怎么办 肝癌二次介入后头晕心慌怎么办 跑步跑得胃疼怎么办 如果世界上的猪都死了怎么办 我和我老婆吵架怎么办 我和我老婆吵架了怎么办 智障人士父母死后怎么办 依赖性太强的人怎么办 高考只有一门写准考证号怎么办 ps做动画不流畅怎么办 ff15引擎剑卖了怎么办 fgo从者满了怎么办 游泳池的水喝了怎么办 月球没有水怎么办秒懂 请了新财神旧的怎么办 报警校体检没过怎么办 ae中建立了文本打不出字怎么办 宝宝3岁内八字怎么办 一岁半宝宝走路内八字怎么办 一岁宝宝走路内八字怎么办 宝宝1岁了内八字怎么办 宝宝学走路内八字怎么办 人胖走路磨腿怎么办 2岁半宝宝内八字怎么办 大狗跑步累倒怎么办? 拉小提琴的姿势不正确怎么办 屁股摔倒了很痛怎么办 腿摔倒了破了怎么办 骑车摔倒腿肿了怎么办