ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

利用udp以大端序发送报文

利用udp以大端序发送报文 问题背景工业上两个设备之间通常以报文的形式进行信息交互。一条报文由若干个字段组成每个字段都能表示一个物理量。例如为了让两个设备知道彼此的位置这两个设备就需要相互发送导航信息报文众所周知导航信息包括经度信息和纬度信息因此该报文就应该包含经度和纬度两个字段所以经度字段和纬度字段就能表示某设备所在位置的经度和纬度这两个物理量。既然一个字段能表示一个物理量那么一个字段一定有字段名、字段数据类型、最低有效位、实际值和编码值。为了讲解清晰我们以经度字段为例其字段名、字段数据类型、最低有效位、实际值和编码值如下表所示字段名字段数据类型最低有效位实际值编码值经度32位有符号整型数0.0001°120.0°十进制1200000十六进制0x0012 4F80由上表可知该设备位置的经度为120.0°且该设备用了一个32位有符号整型数存储经度信息所以我们会很自然地想到一个映射该映射将一个小数实际经度值120.0°映射成一个32位有符号整型数编码值。当编码值为1时其对应的实际值即为最低有效位。所以我们可以看到编码值、实际值和最低有效位的关系为有了编码值A设备就可以通过UDP将该编码值直接发送给B设备B设备利用编码值、最低有效位解算其实际值就知道了A设备的经度。字段定义由问题背景部分可以知道一个字段包含其字段名、字段数据类型、编码值、最低有效位所以我们可以定义如下结构体FieldDefinestruct FieldDefine { QString name; //字段名 DataType type; //数据类型 QString value; // 编码值 double lsb; // 最低有效位 };其中DataType为枚举变量定义如下enum DataType { UINT8, UINT16, UINT32, INT8, INT16, INT32, FLOAT };它表示该字段的数据类型可以是8位无符号整型数、16位无符号整型数、8位有符号整型数、16位有符号整型数、单精度浮点数等。采用大端序对多个字段进行打包一条报文往往由多个字段构成所以可通过FieldDefine的容器存储多个字段具体形式如下QVectorFieldDefine fields所以fields包含了多个字段的编码值。要想发送这些编码值就要采用大端序将这些编码值进行打包打包函数的函数声明如下QByteArray packBigEndian(QVectorFieldDefine fields);由上面的函数声明可知该函数返回类型为QByteArray。QByteArray类是一个专门用来存字节byte的数组。将各个字段的编码值利用大端序的方式存储到QByteArray类型的变量中非常有助于报文的发送。packBigEndian函数的具体定义如下QByteArray UdpSender::packBigEndian( QVectorFieldDefine fields) { QByteArray data; for(auto f:fields) { switch(f.type) { case UINT8: { bool ok; quint8 valuef.value.toUInt(ok, 16); data.append(char(value)); break; } case UINT16: { bool ok; quint16 valuef.value.toUInt(ok, 16); data.append(char(value8)); data.append(char(value)); break; } case UINT32: { bool ok; quint32 valuef.value.toUInt(ok, 16); data.append(char(value24)); data.append(char(value16)); data.append(char(value8)); data.append(char(value)); break; } case INT8: { bool ok; qint8 valuef.value.toInt(ok, 16); data.append(char(value)); break; } case INT16: { bool ok; qint16 valuef.value.toInt(ok, 16); data.append(char(value8)); data.append(char(value)); break; } case INT32: { bool ok; qint32 valuef.value.toInt(ok, 16); data.append(char(value24)); data.append(char(value16)); data.append(char(value8)); data.append(char(value)); break; } case FLOAT: { float valuef.value.toFloat(); quint32 temp; memcpy(temp,value,4); data.append(char(temp24)); data.append(char(temp16)); data.append(char(temp8)); data.append(char(temp)); break; } } } return data; }从上面的函数定义可以看出字段的数据类型不同其处理方式也不同。为具体说明我们以32位整型数为例介绍如何将某个字段的编码值采用大端序的方式存储到QByteArray类型的变量中。首先value是一个十六进制数其大小为4个字节不妨设value0x00123456data.append(char(value24));其次value24表示将value右移24位所以value24变成了0x00char(value24)表示将value24强制转化为char类型即只保留value24的低八位即0x00。然后data.append(char(value24))表示把0x00追加到dataQByteArray类型变量的末尾所以data的第一个元素(data[0])即为0x00.data.append(char(value16));同理value16为0x0012char(value16)取其低8位即0x12data.append(char(value16))表示将0x12追加到data所以data的第二个元素(data[1])为0x12.data.append(char(value8)); data.append(char(value));执行了上面两行代码后我们有data[2]0x34,data[3]0x56。这样一来data就是一个字节数组每个数组表示一个字节且有data[0]0x00 data[1]0x12 data[2]0x34 data[3]0x56所以value(0x00123456)就被拆分成了字节存储到了QByteArray中报文发送void UdpSender::send( QVectorFieldDefine fields) { QByteArray buffer; bufferpackBigEndian(fields); socket-writeDatagram( buffer, QHostAddress(targetIP), targetPort); }以上为将fields发送的函数当执行packBigEndian函数将fields转化为QByteArray类型的变量buffer后我们用socket-writeDatagram函数对其进行发送并指定收方的ip和端口号抓包验证我们通过发送一个32位无符号整型数0x00123456和一个16位无符号整型数0x7892来验证上述内容。通过wireshark抓包我们得到如下报文可以看到报文中包含了00,12,34,56和78,92这几个字节。除此以外我们可以看到第二排出现了两次7f 00 00 01这表示发方和收方的ip是127.0.0.1因为0x7f就是127。第二排的17 70表示发方的端口(6000),13 88表示收方的端口号(5000)。
返回列表