一、为啥我们要分包组包?直接发不行吗?
最初你可能会存在这样的疑惑?所以我们需要了解下TCP和UDP在Qt中的区别。这里参考了阿拉丁的博客 顺便引用他的图;至于TCP和UDP相应的C/S架构图也可以通过这个兄弟的博客了解,当然还有TCP和UDP在Qt中如何实现。
①UDP和TCP表格对比
②在Qt中使用UDP和TCP的一些函数
③阅读关于Qt中UDP的writeDatagram
通过阅读下图的文档便可以知道:UDP一次性发送的数据量是有限的,所以我们必须要分包!!! 至于为啥UDP需要分包,TCP不需要,我这里大胆猜想:TCP其实也是需要拆分的,只不过它内部已经实现了我们组包的机制了,其实就是更牛逼的UDP,而今天我们的主题就是要用UDP实现:阉割版本的TCP。
二、直接开整
①一些需要参考的知识类:Qt的一些文件设备类
参考某知乎博主南理汉子的回答,说实话我这里也有一些没有整清楚,文件IO这块,只能说我中午不能学(早晚都学);没懂这块也可以直接跳过这块,然后知道QByteArray
就行了。
②包头?
包头有啥作用?很明显包头就是用来补充数据包的信息的,举个例子:包头就是诸葛亮留给刘备去江东的三个妙计,刘备带上三个锦囊,去江东遇到困难就打开锦囊获取里头的妙计(这例子有点牵强说实在)。对于一堆数据来说:包头就是诸葛亮的锦囊妙计 当数据到达对端的时候,直接解析包头,获取信息,通过这些信息,把数据依次排序、组装最终重新组合成新的数据,妙哉!!(ps:众所周知:接收端收到的数据包都是乱序的)下面我们来看看包头结构:
③如何分包?
很显然,把数据包切成一块一块的就行了如下图:
在代码中如何体现:通过memcpy
将我们想要的数据,按照指定的大小,拷贝到申请的缓存区即可:需要源码可以去草上爬的博客评论区瞅瞅) 完成把数据切块后,又得把数据包头给加上,很明显还是通过memcpy
我们申请缓存区后,先把包头加在前面,在加入数据,如图:
下面是分包的一个大概流程图:
代码如何体现:
申请数据缓冲区
memcpy(数据缓冲区开始地址,包头数据,包头大小);
memcpy(数据缓冲区开始地址+数据偏移地址,切割的数据,数据大小);
代码如下:切忌不可偷懒直接粘贴代码,下面是一部分代码可供学习参考,如果需要完整源码去“草上爬”的博客评论区获取:
PackageHeader packageHead;//定义一个包头
packageHead.uTransPackageHdrSize=sizeof(packageHead);//包头大小
packageHead.uDataSize = dataLength;//数据的总大小
packageHead.uDataPackageNum = packetNum;//数据被分成包的个数
packageHead.wOpcode= nOpcode;
unsigned char frameBuffer[1024*1000];//设置一个缓冲区
memset(frameBuffer,0,1024*1000);//缓冲区给初始化一下
while (currentPacketIndex < packetNum)//如果 下标<包数量
{
if (currentPacketIndex < (packetNum-1))//如果不是最后一个包
{
packageHead.uTransPackageSize = sizeof(PackageHeader)+UDP_MAX_SIZE;//包的大小=包头大小+最大的数据大小
packageHead.uDataPackageCurrIndex = currentPacketIndex+1;//当前下标+1
packageHead.uDataPackageOffset = (currentPacketIndex)*UDP_MAX_SIZE;//偏移量=下标*数据包最大量
memcpy(frameBuffer, &packageHead, sizeof(PackageHeader));//拷贝包头到缓存区
memcpy(frameBuffer+sizeof(PackageHeader), dataBuffer+packageHead.uDataPackageOffset, UDP_MAX_SIZE);//包头后面+数据
int length=my_udp->writeDatagram(
(const char*)frameBuffer, packageHead.uTransPackageSize,
my_ip_port.ip, my_ip_port.port);
if(length!=packageHead.uTransPackageSize)
{
qDebug()<<"Failed to send image";
}
else
{
// ui->msg_record->append("发送成功包:"+currentPacketIndex);
qDebug()<<"Success to send image";
}
currentPacketIndex++;
}
④如何组包?
话不多说,直接上图,同样需要一块缓存区,然后通过memcpy
把接收到的数据给分为:包头+数据 然后放进申请的缓存区进行取出包头,取出数据,拼接等一系列操作就行了。
下面是组包流程图:
来点代码意思意思:直接CV是跑步不起来的
PackageHeader *packageHead = (PackageHeader *)datagram.data();
if (packageHead->uDataPackageCurrIndex == num)//如果下标为1则
{
num++;//下标++
size += packageHead->uTransPackageSize-packageHead->uTransPackageHdrSize;//大小为:当前包大小-包头大小
if (size > 1024*1000)
{
ui->msg_record->append("image too big");
num = 1;
size = 0;
return;
}
if (packageHead->uDataPackageOffset > 1024*1000)
{
ui->msg_record->append("image too big");
packageHead->uDataPackageOffset = 0;
num = 1;
size = 0;
return;
}
memcpy(imageData.data+packageHead->uDataPackageOffset, datagram.data()+packageHead->uTransPackageHdrSize,
packageHead->uTransPackageSize-packageHead->uTransPackageHdrSize);
三、总结
关于解决这个问题: 研究别人的论文确实是以一种解决问题的好途径,CSDN上确实是 “在垃圾堆里找宝贝”。
关于网络协议这块一点不足: 需要从底层认真研究网络协议,好好把王道书再瞅瞅。
关于后续安排: 马上踏入工作岗位,得加强对C++的学习,希望一切顺利。