前言
虽然QT中,udp发送和接收,其实非常简单,但是实际工作中,其实涉及到帧头帧尾,字节对齐,以及大小端序的问题。比如网络中,正规的一般都是大端序,而不是小端序,大多数的系统中,默认存储的都是小端序。 其次,udp指令,在代码中,我们一般都会设置结构体,如果是小端可以直接使用结果体转换,如果是大端序,需要先将数据转尾大端然后发送,接收端再将大端序数据转为小端进行分析和保存。以下是一个实际案例的测试demo:
注意:
以下协议和帧头等都是根据近期项目随机定的,不涉及保密。
指令接收端口为18000; 目的是开启视频保存。
发送端:
main.cpp
#include <QUdpSocket>
#include <QtEndian>
#include <QDataStream>
// 以1字节对齐:
#pragma pack(1)
struct SaveVideoCtrlPara
{
// 帧头 0xaa56
unsigned short dataHead = 0xbb58;
// 第一路
quint8 video1_isSave = 0x00; // 0x00:close 0x01:start save
quint8 video1_type = 0x00; // 0x00:null 0x01:rtsp 0x02:rtp
quint32 video1_para = 0x00; // rtsp: IP rtp :recv port
quint32 video1_reserve = 0x00; // 预留
char video1_fileName[64] = ""; // 保存文件名
// 第一路
quint8 video2_isSave = 0x00; // 0x00:close 0x01:start save
quint8 video2_type = 0x00; // 0x00:null 0x01:rtsp 0x02:rtp
quint32 video2_para = 0x00; // rtsp: IP rtp :recv port
quint32 video2_reserve = 0x00; // 预留
char video2_fileName[64] = ""; // 保存文件名
// 包尾
// unsigned short dataTail = 0x1111;
};
#pragma pack(pop)
// 转为大端序
QByteArray serializeStruct(const SaveVideoCtrlPara &data) {
QByteArray byteArray;
QDataStream stream(&byteArray, QIODevice::WriteOnly);
// 将数据转换为大端序
stream << qToBigEndian(data.dataHead);
stream << qToBigEndian(data.video1_isSave);
stream << qToBigEndian(data.video1_type);
stream << qToBigEndian(data.video1_para);
stream << qToBigEndian(data.video1_reserve);
stream << QString(data.video1_fileName).toUtf8();
stream << qToBigEndian(data.video2_isSave);
stream << qToBigEndian(data.video2_type);
stream << qToBigEndian(data.video2_para);
stream << qToBigEndian(data.video2_reserve);
stream << QString(data.video2_fileName).toUtf8();
return byteArray;
}
int main(int argc, char *argv[])
{
QByteArray temp_byte_array;
SaveVideoCtrlPara test;
test.video1_type = 0x02;
test.video1_para = 10011;
strcpy(test.video1_fileName,"test1");
test.video2_type = 0x02;
test.video2_para = 10012;
strcpy(test.video2_fileName,"test2");
QUdpSocket *m_pUdpSocket = new QUdpSocket();
m_pUdpSocket->bind(QHostAddress("127.0.0.1"),18001);
// 小端序
// QByteArray frame = QByteArray((char *)&test, sizeof(SaveVideoCtrlPara));
// 大端序
QByteArray frame = serializeStruct(test);
// send
m_pUdpSocket->writeDatagram(frame, QHostAddress("127.0.0.1"), 18000);
}
接收端:
main.cpp
#include <QApplication>
#include <QUdpSocket>
#include <QDataStream>
#include <QtEndian>
// 以1字节对齐:
#pragma pack(1)
struct SaveVideoCtrlPara
{
// 帧头 0xaa56
unsigned short dataHead = 0xbb58;
// 第一路
quint8 video1_isSave = 0x00; // 0x00:close 0x01:start save
quint8 video1_type = 0x00; // 0x00:null 0x01:rtsp 0x02:rtp
quint32 video1_para = 0x00; // rtsp: IP rtp :recv port
quint32 video1_reserve = 0x00; // 预留
char video1_fileName[64] = ""; // 保存文件名
// 第一路
quint8 video2_isSave = 0x00; // 0x00:close 0x01:start save
quint8 video2_type = 0x00; // 0x00:null 0x01:rtsp 0x02:rtp
quint32 video2_para = 0x00; // rtsp: IP rtp :recv port
quint32 video2_reserve = 0x00; // 预留
char video2_fileName[64] = ""; // 保存文件名
// 包尾
// unsigned short dataTail = 0x1111;
};
#pragma pack(pop)
SaveVideoCtrlPara deserializeStruct(const QByteArray &byteArray)
{
QDataStream stream(byteArray);
QByteArray tmp_str1,tmp_str2;
//tmp_str1.resize(64);
//tmp_str2.resize(64);
stream.setByteOrder(QDataStream::BigEndian); // 设置为大端序
SaveVideoCtrlPara data;
// 从数据流中读取并转换为小端序
stream >> data.dataHead;
stream >> data.video1_isSave;
stream >> data.video1_type;
stream >> data.video1_para;
stream >> data.video1_reserve;
stream >> tmp_str1;
stream >> data.video2_isSave;
stream >> data.video2_type;
stream >> data.video2_para;
stream >> data.video2_reserve;
stream >> tmp_str2;
data.dataHead = qFromBigEndian(data.dataHead);
data.video1_isSave = qFromBigEndian(data.video1_isSave);
data.video1_type = qFromBigEndian(data.video1_type);
data.video1_para = qFromBigEndian(data.video1_para);
data.video1_reserve = qFromBigEndian(data.video1_reserve);
strcpy(data.video1_fileName, tmp_str1.toStdString().c_str());
data.video2_isSave = qFromBigEndian(data.video2_isSave);
data.video2_type = qFromBigEndian(data.video2_type);
data.video2_para = qFromBigEndian(data.video2_para);
data.video2_reserve = qFromBigEndian(data.video2_reserve);
strcpy(data.video2_fileName, tmp_str2.toStdString().c_str());
return data;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QUdpSocket *m_pUdpSocket = new QUdpSocket();
m_pUdpSocket->bind(QHostAddress("127.0.0.1"),18000);
QObject::connect(m_pUdpSocket,&QUdpSocket::readyRead,[=](){
QByteArray frame;
while(m_pUdpSocket->hasPendingDatagrams())
{
frame.resize(m_pUdpSocket->pendingDatagramSize());
// 接收数据报,将其存放到datagram中
m_pUdpSocket->readDatagram(frame.data(), frame.size());
// 大端序
SaveVideoCtrlPara pFrame = deserializeStruct(frame);
if(pFrame.dataHead == 0xaa56)
{
qDebug()<<"接收到信息*********";
qDebug()<<"video1_isSave:" <<pFrame.video1_isSave;
qDebug()<<"video1_type :" <<pFrame.video1_type ;
if(pFrame.video1_type==0x01)
qDebug()<<"video1_para :" <<QHostAddress(pFrame.video1_para).toString();
else
qDebug()<<"video1_para :" <<pFrame.video1_para;
qDebug()<<"video1_fileName:"<<pFrame.video1_fileName;
qDebug()<<"video2_isSave:" <<pFrame.video2_isSave;
qDebug()<<"video2_type :" <<pFrame.video2_type ;
if(pFrame.video2_type==0x01)
qDebug()<<"video2_para :" <<QHostAddress(pFrame.video2_para).toString();
else
qDebug()<<"video2_para :" <<pFrame.video2_para;
qDebug()<<"video2_fileName:"<<pFrame.video2_fileName;
qDebug()<<"*****************";
}
// // 小端序
// struct SaveVideoCtrlPara *pFrame = (struct SaveVideoCtrlPara *)frame.data();
// if(pFrame->dataHead == 0xaa56)
// {
// qDebug()<<"接收到信息*********";
// qDebug()<<"video1_isSave:" <<pFrame->video1_isSave;
// qDebug()<<"video1_type :" <<pFrame->video1_type ;
// if(pFrame->video1_type==0x01)
// qDebug()<<"video1_para :" <<QHostAddress(pFrame->video1_para).toString();
// else
// qDebug()<<"video1_para :" <<pFrame->video1_para;
// qDebug()<<"video1_fileName:"<<pFrame->video1_fileName;
// qDebug()<<"video2_isSave:" <<pFrame->video2_isSave;
// qDebug()<<"video2_type :" <<pFrame->video2_type ;
// if(pFrame->video2_type==0x01)
// qDebug()<<"video2_para :" <<QHostAddress(pFrame->video2_para).toString();
// else
// qDebug()<<"video2_para :" <<pFrame->video2_para;
// qDebug()<<"video2_fileName:"<<pFrame->video2_fileName;
// qDebug()<<"*****************";
// }
}
});
return app.exec();
}
结论
大端序代码打印结果:
小端序代码打印结果:
由代码可知,大端序要稍微复杂一些,但是符合互联网传输数据的要求,小端序的实现要简单不少,所以如果是非正规场合,使用小端是一种简洁的方式。