C++中TCP/IP按约定报文协议接收数据完成拼包

有段时间没有更新博客了,近来比较忙,没有顾上写博客。终于完成了一个大任务,有时间回顾一下这段时间的成果。这篇博客,先介绍和总结一下很久前的工作。TCP/IP接收数据拼包。由于时间太长很多东西记不清楚了,请见谅。

任务是某设备通过WIFI以TCP/IP的协议发送图像数据,数据按照规定的报文协议接收数据。

报文内容分为控制域(8个字节)与数据域(不定长),报文的启动字符为0628H占两个字节,接下来两个字节是报文长度(除去控制域本身之外的所有字节长度,因此加上启动字符在内的完整的报文为报文长度的值+8个字节)。控制域后面4个字节预留。数据域前2个字节为数据类型,接下来2个字节为数据内容长度,接下来2个字节为帧类型,接下来2个字节标志位标示是否有后续帧,然后是真正的图像数据内容(由于图像数据内容很大,一帧报文数据可能发不完,因此分多帧发送,后续帧标志位就标志某一帧图像数据是否发完)。

然后约定,所有数据类型按小端对齐(低字节在前。当然也可以约定大端对齐,高字节在前。)

好的,协议定好,接下来就开始发送数据和接收数据把。不过,发送数据的工作不在我这边,我只负责接收。不过,发送数据的工作跟接收数据的工作可以互相参考一下。整理一下我的任务,我需要通过TCP接收发过来的数据,识别出启动字符和报文长度,然后按报文长度的值接收报文。接收完一帧报文后,开始解包操作。由于一帧完整的图像可能分多帧报文发,所以,解包的时候需要注意是否有后续帧,数据是否完整了。

好,网上如何TCP接收数据的代码很多,先上代码。

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include<sys/socket.h>  
  3. #include<arpa/inet.h> //inet_addr  
  4. #include<netdb.h> //hostent  
  5. #include <sys/types.h>  
  6. #include <assert.h>  
  7. #include<string.h> //strcpy  
  8. #include<map>  
  9. #include<vector>  
  10. #include<fstream>  
  11. #include<unistd.h>  
  12. using namespace std;  
  13. #pragma pack(push, 1)  
  14. static int stepSize=0;  
  15.   
  16.   
  17. void handleDataUint(char *dataUnit, int size)  
  18. {  
  19.   //得到数据之后,在这里进行拼包或者进行下一步处理等操作  
  20. }  
  21.   
  22. int main(int argc, char **argv)  
  23. {  
  24.      
  25.       int socket_desc,rcv_size;  
  26.       int err=-1;  
  27.       socklen_t optlen;  
  28.       struct sockaddr_in server;//定义服务器的相关参数  
  29.       char server_reply[5000];  
  30.   
  31.       //Create socket  
  32.       //下面的AF_INET也可以用PF_INET。AF_INET主要是用于互联网地址,而 PF_INET 是协议相关,通常是sockets和端口  
  33.       socket_desc = socket(AF_INET , SOCK_STREAM , 0);//第二个参数是套接口的类型:SOCK_STREAM或SOCK_DGRAM。第三个参数设置为0。  
  34.       if (socket_desc == -1)  
  35.       {  
  36.           printf("Could not create socket");  
  37.       }  
  38.       rcv_size = 4*640000;    /* 接收缓冲区大小为4*640K */  
  39.       optlen = sizeof(rcv_size);  
  40.       err = setsockopt(socket_desc,SOL_SOCKET,SO_RCVBUF, (char *)&rcv_size, optlen);//设置套接字,返回值为-1时则设置失败  
  41.       if(err<0){  
  42.               printf("设置接收缓冲区大小错误\n");  
  43.       }  
  44.       server.sin_addr.s_addr = inet_addr("192.168.10.2");//服务器IP地址  
  45.       server.sin_family = AF_INET;//对应与socket,也可选PF_INET  
  46.       server.sin_port = htons( 52404 );//端口号  
  47.   
  48.       if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)//用建立的socket尝试同设置好的服务器connect  
  49.       {  
  50.           perror("connect error:");  
  51.           return 1;  
  52.       }  
  53.       printf("Connected");  
  54.   
  55.       char recbuff[800000];//接收的数据缓存大小,这是我自己设置的区域,为了存放报文  
  56.       unsigned long buffsize;  
  57.       int packetCount = 0;  
  58.   
  59.   
  60.       buffsize=0;//该值标示当前缓存数据的大小,及下一帧数据存放的地址  
  61.           while (1) {  
  62.   
  63.   
  64.               int readSize = recv(socket_desc, server_reply , sizeof(server_reply) , 0);//从服务器接收数据,其中第三个参数为单次接收的数据大小  
  65. //同上面的rcv_size区分开,上面的rcv_size是TCP/IP的机理,传输过程中,数据会暂时先存储在rcv_size里.  
  66. //然后你recv再从rcv_size这个缓冲里取你设置的sizeof(serve_reply)的数据。其中readSize为recv的返回值,该值返回你实际接收到的数据大小,这点要注意。  
  67. //接收到的数据放在server_reply[5000]里面  
  68.               if(readSize < 0) {//实际接收到的数据为负,表示接收出错  
  69.                   printf("recv failed");  
  70.                   //should shut down connection and reconnect!  
  71.                   assert(false);  
  72.                   return 1;  
  73.               }  
  74.               else if (readSize == 0) {//表示无数据传输,接收到数据为0  
  75.                   printf("readSize=0");  
  76.                   break;  
  77.               }  
  78.               ++packetCount;//接收到包的次数加1  
  79.   
  80.               memcpy(recbuff + buffsize, server_reply, readSize);//memcpy为内存拷贝函数,将该次接收到的server_reply的数据拷贝到recbuff里  
  81. //其中+buffsize,从recbuff头地址+buffsize的地址开始拷贝(第一次buffsize=0,及从头开始拷贝)拷贝的大小为readSize,即recv实际接收到的数据大小。  
  82.               buffsize += readSize;//buffsize=buffsize+readSize,此时buffsize指向该次拷贝的数据大小的下一位。  
  83.               const int packetHeadSize = 8;//定义控制域的数据大小为8个字节  
  84.               static int expectedPacketSize = -1;//定义期望得到的数据包大小为-1,以此判断本次接收到的数据是否是数据头部  
  85.               if (expectedPacketSize == -1 && buffsize >= packetHeadSize) {//接收到的数据比8个字节大,即包含了控制域及数据域,则进行下一步分析  
  86.                   //start token must be 0x0628, otherwise reconnect!  
  87.                   unsigned char t0 = recbuff[0];//取出接收数据的前两个字节  
  88.                   unsigned char t1 = recbuff[1];  
  89.                   assert(t0 == 0x28 && t1 == 0x06);//判断是否为0628H,是否符合启动字符条件,注意小端对齐  
  90.                   if (!(t0 == 0x28 && t1 == 0x06))//如果不是0628H,则表示该次数据有误  
  91.                   {  
  92.                       return 1;  
  93.                   }  
  94.                   //find packet length!!  
  95.   
  96.                   unsigned short len = 0;//定义报文长度  
  97.                   memcpy(&len, recbuff + 2, 2);//将接收数据的下第三第四个字节付给len,根据协议第三第四个字节存储的是该帧报文的长度  
  98.                   expectedPacketSize = len + packetHeadSize;//则期望得到的数据包大小为报文长度加控制域长度  
  99.               }  
  100.   
  101.               //get one whole packet!  
  102.               if (expectedPacketSize != -1 && buffsize >= expectedPacketSize) {//当期望得到的数据包大小不是-1  
  103.                   // 并且recbuff里接收到的数据大小已经大于所需要的数据大小,如果接收到的数据小于完整报文的长度,则继续接收  
  104.                   //  
  105.                   //下面为接收到的完整的一帧报文,定义了一个解包函数负责解包,从缓存数据的第9个字节开始取,取完整数据域长度的数据,即只取数据域的内容  
  106.                   handleDataUint(recbuff + packetHeadSize, expectedPacketSize -packetHeadSize);  
  107. //下面的memmove函数是内存移动函数,将下一帧报文移动到recbuff的起始处,覆盖掉已经取出的数据  
  108.                   memmove(recbuff, recbuff + expectedPacketSize, buffsize - expectedPacketSize);  
  109.                   buffsize -= expectedPacketSize;  
  110.                   expectedPacketSize = -1;  
  111.               }  
  112.           }  
  113.       return 0;  
  114. }  
  115. #pragma pack(pop)  

其中,涉及到缓存区的数据处理,主要是memcpy,memmove等函数的使用,且buffsize,expectedPacketSize等数据大小的使用。buffsize不仅可以表示目前recbuff缓存区已经存入的数据大小,而且还表征了下一帧要存放的数据地址。而expectedPacketSize=-1可以用于判断某次recv接收到的数据是否完毕,是否含有报文的开头,不等于-1的时候又可以表示期望获得的完整一帧报文的数据大小。

由于TCP/IP的限制,一帧很大的报文需要分次多次发送,这样就需要将多次发送过来的包进行拼包处理,已避免粘包等情况。

以上是我用C++的拼包的代码。希望有用哈~

时间太长,很多别的东西记不住了,就先这样吧,我得去忙了。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值