在学习QT的过程,客户端/服务端 收发自定义复杂数据包(结构体带指针的不定长数据)的处理
示例如下,首先在头文件中申明这个自定义结构体(收发部分都需要包含这个结构体定义)
其中char pValue[0]表示这个是变长的部分!!!
#pragma pack(1)
struct DHPacket
{
int len;
char pValue[0];
};
#pragma pack()
一定要记得#pragma pack(1)【copy 一段别人的解释 :) (在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。)】
在发送端,组织代码,进行包装,即
DHPacket* dhPacket = new DHPacket();
dhPacket->len = len;//把真实的数据长度放在这个dhPacket的成员变量len里(PS:这个真实数据指的是实际放在char pValue[0]里面的内容)
接着申请长度为这个sizeof(DHPacket)+len的char* allData数据,
并将其前【0~sizeof(DHPacket)-1】的内容填充为dhPacket的值;
接着将其【sizeof(DHPacket)~sizeof(DHPacket)+len-1】的值填充为真实的数据
这样就可以将其系列化的数据发送出去
QString info;
info = lineEdit->text();
info += "\0";
if(info.length() > 0)
{//有数据
//自己定义DHPacket 组装发送给客户端
const char* data = info.toStdString().data();
int len = strlen(data);
int packetSize = sizeof (DHPacket);
DHPacket* dhPacket = new DHPacket();
dhPacket->len = len;
char* allData = new char[sizeof(DHPacket)+len];
memcpy(allData,(char*)dhPacket,sizeof(DHPacket));
memcpy(allData+sizeof (DHPacket),data,len);
if(udpSocket->writeDatagram(
allData,sizeof(DHPacket)+len,QHostAddress::Broadcast,portID)
!= sizeof(DHPacket)+len)
{
QMessageBox::information(this,"提示",
QString("%1 广播失败").arg(info));
}
free(allData);
}
接着在接收端,来解析收到的数据包。
首先将所有的数据先接受下来:即
int allDataLen = clientSocket->pendingDatagramSize();
char* alldata = new char[allDataLen];
clientSocket->readDatagram((char*)alldata,allDataLen);
所有的数据就放在alldata里面了,
接着获取DHPacket的信息,并得到真实数据的长度
DHPacket* dhPacket = new DHPacket;
memcpy(dhPacket,alldata,sizeof (DHPacket));
接着再申请一个真实数据长度+1的字符指针,并将其最后一位填充为‘\0’(即字符串结束符)【如果最后这个真实数据不是拿去显示字符串的,这里这个真实数据长度不用+1 去申请】
void UdpClient::ReceiveData()
{
//自己定义DHPacket 从服务器端收数据
if(clientSocket->hasPendingDatagrams())
{
int allDataLen = clientSocket->pendingDatagramSize();
char* alldata = new char[allDataLen];
clientSocket->readDatagram((char*)alldata,allDataLen);
DHPacket* dhPacket = new DHPacket;
memcpy(dhPacket,alldata,sizeof (DHPacket));
int realDataLen = dhPacket->len;//获取结构体中标志实际数据的长度
if(realDataLen >= 0)
{
char* realData = new char[realDataLen+1];//长度+1,给其最后一位给'\0'字符串结束符
memcpy(realData,alldata+sizeof(DHPacket),realDataLen);
realData[realDataLen] = '\0';//字符串加上最后一位结束符
QString recevie(realData);
recevie += "\n";
recevieTE->insertPlainText(recevie);
}
if(alldata != nullptr)
delete []alldata;
}
}
最后效果如下,完美发送自定义结构体(含有指针的)内容的收发 :)
对于更复杂的数据包,更常用的我也实现了,后面附上关键代码
首先包的定义(包头+内容)
#pragma pack(1)
struct DHPacket
{
int len;
char pValue[0];
};
enum enPacketType
{
PACKET_CONNECT = 0,
PACKET_CONTENT,
PACKET_BIG_CONTENT,
PACKET_HEART_BEAT,//心跳包 只对TCP进行
};
struct DHPacketHead
{
int type;//1:PacketConnect 2:PacketContent
int len;
char pValue[0];
};
struct DHPacketHeadEx
{
enPacketType packetType;
int len;
char pValue[0];
DHPacketHeadEx()
{
packetType = enPacketType::PACKET_CONNECT;
}
};
struct PacketConnect
{
bool isConnect;
};
struct PacketContent
{
char contents[1024];
};
struct PacketBigContent
{
char ip[1024];
char name[1024];
PacketBigContent()
{
memset(ip,0,sizeof(ip));
memset(name,0,sizeof(name));
}
};
struct PacketHeartBeat
{//心跳包 就弄个空包
};
#pragma pack()
发送部分代码如下:
void UdpServer::SendConnect()
{
static bool isConnect = true;
char *bufs = new char[sizeof(DHPacketHead) + sizeof(PacketConnect)];
DHPacketHead* dhPacket = (DHPacketHead*)bufs;
dhPacket->type = 1;
dhPacket->len = sizeof(PacketConnect);
PacketConnect* packetConnet = (PacketConnect*)dhPacket->pValue;
packetConnet->isConnect = isConnect;
isConnect = !isConnect;
if(udpSocket->writeDatagram(
bufs,sizeof(DHPacketHead) + sizeof(PacketConnect),QHostAddress::Broadcast,portID)
!= sizeof(DHPacketHead) + sizeof(PacketConnect))
{
QMessageBox::information(this,"提示",
QString("%1 广播失败").arg("PacketConnect"));
}
}
void UdpServer::SendContent()
{
QString info;
info = lineEdit->text();
const char* data = info.toStdString().data();
char *bufs = new char[sizeof(DHPacketHead) + sizeof(PacketContent)];
DHPacketHead* dhPacket = (DHPacketHead*)bufs;
dhPacket->type = 2;
dhPacket->len = sizeof(PacketContent);
PacketContent* packetContent = (PacketContent*)dhPacket->pValue;
strcpy(packetContent->contents,data);
if(udpSocket->writeDatagram(
bufs,sizeof(DHPacketHead) + sizeof(PacketContent),QHostAddress::Broadcast,portID)
!= sizeof(DHPacketHead) + sizeof(PacketContent))
{
QMessageBox::information(this,"提示",
QString("%1 广播失败").arg("PacketContent"));
}
delete [] bufs;
}
void UdpServer::SendBigContent()
{
char* sendData = new char[sizeof (DHPacketHeadEx) + sizeof(PacketBigContent)];
DHPacketHeadEx* headEx = (DHPacketHeadEx*)sendData;
headEx->len = sizeof(sizeof(PacketBigContent));
headEx->packetType = enPacketType::PACKET_BIG_CONTENT;
PacketBigContent* bigContent = (PacketBigContent*)headEx->pValue;
QString localHostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(localHostName);
//获取主机IP地址
QList<QHostAddress> listAddr = hostInfo.addresses();
QString allAddrs;
for(int index;index < listAddr.length();index++)
{
QHostAddress perAddr = listAddr.at(index);
QString addr = perAddr.toString();
addr += "\t";
allAddrs += addr;
}
allAddrs+="\t";
memcpy(bigContent->ip,allAddrs.toStdString().data(),
strlen(allAddrs.toStdString().data()));
memcpy(bigContent->name,localHostName.toStdString().data(),
strlen(localHostName.toStdString().data()));
if(udpSocket->writeDatagram(
(char*)headEx,sizeof (DHPacketHeadEx) + sizeof(PacketBigContent),QHostAddress::Broadcast,portID)
!= sizeof (DHPacketHeadEx) + sizeof(PacketBigContent))
{
QMessageBox::information(this,"提示",
QString("%1 广播失败").arg("PacketContent"));
}
else
{
QMessageBox::information(this,"提示",
QString("发送成功 IP:%1\r\nname:%2").arg(allAddrs).arg(localHostName));
}
delete [] sendData;
}
void UdpServer::SendHeartBeat()
{
char* sendData = new char[sizeof (DHPacketHeadEx) + sizeof(PacketHeartBeat)];
DHPacketHeadEx* headEx = (DHPacketHeadEx*)sendData;
headEx->len = sizeof(sizeof(PacketBigContent));
headEx->packetType = enPacketType::PACKET_HEART_BEAT;
if(udpSocket->writeDatagram(
(char*)headEx,sizeof (DHPacketHeadEx) + sizeof(PacketHeartBeat),QHostAddress::Broadcast,portID)
!= sizeof (DHPacketHeadEx) + sizeof(PacketHeartBeat))
{
QMessageBox::information(this,"提示",
QString("%1 广播失败").arg("心跳包"));
}
else
{
QMessageBox::information(this,"提示",
QString("发送成功 心跳包"));
}
delete [] sendData;
}
接收部分如下
void UdpClient::ReceiveDataEx()
{
//自己定义DHPacketHeadEx+内容 从服务器端收数据
if(clientSocket->hasPendingDatagrams())
{
int allDataLen = clientSocket->pendingDatagramSize();
char* alldata = new char[allDataLen];
clientSocket->readDatagram((char*)alldata,allDataLen);
if(allDataLen > sizeof (DHPacketHeadEx))
{//长度才正常
DHPacketHeadEx* head = (DHPacketHeadEx*)alldata;
switch(head->packetType)
{
case enPacketType::PACKET_BIG_CONTENT:
//服务器端发的大报文
{
PacketBigContent* pData = (PacketBigContent*)head->pValue;
QString strInfo = QString("服务器发来连接内容\r\nip:%1 \t name= %2").arg(QString(pData->ip)).arg(QString(pData->name));
strInfo+="\n";
recevieTE->insertPlainText(strInfo);
}
break;
case enPacketType::PACKET_HEART_BEAT:
{
QString strInfo = QString("服务器发来心跳包");
strInfo+="\n";
recevieTE->insertPlainText(strInfo);
}
break;
case enPacketType::PACKET_CONNECT:
break;
case enPacketType::PACKET_CONTENT:
break;
default:
//错误的报文
break;
}
}
}
}
最后效果如下图:
后面附上三张图,讲述实际组织数据包收发