前言
最近的项目用到UDP接收结构体,以为和普通的传送字符串的一样,没想到我还是太天真。要能够接收或者传送结构体,一个很重要的知识点是:结构体字节对齐。废话不多说,小课堂开始了!
结构体对齐
参考https://www.cnblogs.com/codingnutter/p/5634482.html
许多计算机系统对基本数据类型合法的进行了一些限制,要求某种类型对象的地址必须是某个值(通常是2,4 和8)的倍数。这种对齐简化了形成处理器与存储系统之间的接口的硬件设计。当数据结构为结构体时,为了满足这种数据对齐的机制,编译器可能需要在结构体的字段的分配中插入间隙--向结构体中最大的元素对齐。
typedef struct
{
char c;
int i[2];
double v;
}S;
看上面的结构体,在没有数据对齐的情况下,size()=1+4*2+8=17字节。
再编译器数据对齐处理后,他的结构大小变成了24字节(向结构体中最大的元素对齐),内存布局为
这个就是为什么我强转后,结构体中的部分数据是乱码的。
改进后(结构体一字节对齐)
在听取网友意见后,发现将结构体一字节对齐后,操作真的是非常简单。
结构体一字节对齐:
#pragma pack(1) //指定一字节对齐
struct Test_data{
int iNumber;
char arrchResult[45];
char arrchCode[12];
bool bOutLimit_Flag;
int iMark;
BYTE byteResultType;
};
#pragma pack() //取消指定对齐,恢复缺省对齐
直接发送或接收
data.iNumber=1;
strcpy(data.arrchResult,"hello");
strcpy(data.arrchCode,"0x29");
data.bOutLimit_Flag=true;
data.iMark=200;
data.byteResultType=23;
//发送
udpSocket->writeDatagram((char *)&data,sizeof(data),QHostAddress::Broadcast,port);
Test_data datagram;
//接收
udpSocket->readDatagram((char*)&datagram,sizeof(datagram));
UDP发送和接收结构体消息(最原始的做法)
知道结构体的数据对齐的处理后,我们知道不能进行强转了,那么该怎么做呢?
我的做法是:将struct中的元素按字节大小一个个的存放到QByteArray中,QByteArray是连续的,接收时按大小再取出来就可以了。
直接上代码:
服务端(发送数据)
//udpserver.h
#include <QWidget>
#include<QVBoxLayout>
#include<QtNetwork/QUdpSocket>
#include<QTimer>
#include<QPushButton>
#define BYTE unsigned char
struct Test_data{
int iNumber;
char arrchResult[45];
char arrchCode[12];
bool bOutLimit_Flag;
int iMark;
BYTE byteResultType;
};
class UdpServer : public QWidget
{
Q_OBJECT
public:
UdpServer(QWidget *parent = 0);
public slots:
void StartBtnClicked();
void timeout();
private:
QPushButton *startBtn;
QVBoxLayout *mainLayout;
int port;
bool isStarted;
QUdpSocket *udpSocket;
QTimer *timer;
QByteArray m_byteArray;
};
#endif // UDPSERVER_H
//udepserver.cpp
#include "udpserver.h"
#include<QtNetwork/QHostAddress>
#include<QDebug>
UdpServer::UdpServer(QWidget *parent)
: QWidget(parent)
{
//初始化
Test_data data;
data.iNumber=1;
strcpy(data.arrchResult,"hello");
strcpy(data.arrchCode,"0x29");
data.bOutLimit_Flag=true;
data.iMark=200;
data.byteResultType=23;
qDebug()<<sizeof(data);
//结构体转byteArray
byteArray.append((char *)&data.iNumber,sizeof(data.iNumber));
byteArray.append(data.arrchResult,sizeof(data.arrchResult));
byteArray.append(data.arrchCode,sizeof(data.arrchCode));
byteArray.append((char *)&data.bOutLimit_Flag,sizeof(data.bOutLimit_Flag));
byteArray.append((char *)&data.iMark,sizeof(data.iMark));
byteArray.append((char *)&data.byteResultType,sizeof(data.byteResultType));
setWindowTitle(tr("UDP Server"));
startBtn=new QPushButton(tr("start"),this);
mainLayout=new QVBoxLayout(this);
mainLayout->addWidget(startBtn);
connect(startBtn,SIGNAL(clicked(bool)),this,SLOT(StartBtnClicked()));
port=5555;
isStarted=false;
udpSocket=new QUdpSocket(this);
timer=new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(timeout()));
}
void UdpServer::StartBtnClicked()
{
if(!isStarted)
{
startBtn->setText(tr("stop"));
timer->start(1000);
isStarted=true;
}
else
{
startBtn->setText(tr("start"));
isStarted=false;
timer->stop();
}
}
void UdpServer::timeout()
{
udpSocket->writeDatagram(byteArray.data(),byteArray.size(),QHostAddress::Broadcast,port);
}
客户端接收数据
#ifndef UDPCLIENT_H
#define UDPCLIENT_H
#include <QWidget>
#include<QtNetwork/QUdpSocket>
#include<QByteArray>
#define BYTE unsigned char
struct Test_data{
int iNumber;
char arrchResult[45];
char arrchCode[12];
bool bOutLimit_Flag;
int iMark;
BYTE byteResultType;
};
class UdpClient : public QWidget
{
Q_OBJECT
public:
UdpClient(QWidget *parent = 0);
public slots:
void dataReceived();
private:
int port;
QUdpSocket *udpSocket;
};
#endif // UDPCLIENT_H
#include "udpclient.h"
#include<QMessageBox>
#include<QtNetwork/QHostAddress>
#include<QDebug>
UdpClient::UdpClient(QWidget *parent)
: QWidget(parent)
{
setWindowTitle(tr("UDP Client"));
port=5555;
udpSocket=new QUdpSocket(this);
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
bool result=udpSocket->bind(port); //绑定端口
if(!result)
{
QMessageBox::information(this,tr("error"),tr("udp socket create error"));
return;
}
}
void UdpClient::dataReceived()
{
while(udpSocket->hasPendingDatagrams()) //有未处理的报文
{
Test_data* datagram=new Test_data; //用于存放接收的数据
QByteArray recvMsg;
qDebug()<<udpSocket->pendingDatagramSize();
recvMsg.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(recvMsg.data(),recvMsg.size());
int pos=0;
memcpy(&datagram->iNumber,recvMsg.constData(),sizeof(datagram->iNumber));
pos+=sizeof(datagram->iNumber);
memcpy(datagram->arrchResult,recvMsg.constData()+pos,sizeof(datagram->arrchResult));
pos+=sizeof(datagram->arrchResult);
memcpy(datagram->arrchCode,recvMsg.constData()+pos,sizeof(datagram->arrchCode));
pos+=sizeof(datagram->arrchCode);
memcpy(&datagram->bOutLimit_Flag,recvMsg.constData()+pos,sizeof(datagram->bOutLimit_Flag));
pos+=sizeof(datagram->bOutLimit_Flag);
memcpy(&datagram->iMark,recvMsg.constData()+pos,sizeof(datagram->iMark));
pos+=sizeof(datagram->iMark);
memcpy(&datagram->byteResultType,recvMsg.constData()+pos,sizeof(datagram->byteResultType));
qDebug()<<datagram->iNumber;
qDebug()<<datagram->arrchResult;
qDebug()<<datagram->arrchCode;
qDebug()<<datagram->bOutLimit_Flag;
qDebug()<<datagram->iMark;
qDebug()<<datagram->byteResultType;
}
}
结束语
在恶作剧的心情过后,我把改进后的方法提前了 。