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

来源:互联网 发布:执信软件 编辑:程序博客网 时间:2024/05/29 07:11

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

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

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

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

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

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

#include <stdio.h>#include<sys/socket.h>#include<arpa/inet.h> //inet_addr#include<netdb.h> //hostent#include <sys/types.h>#include <assert.h>#include<string.h> //strcpy#include<map>#include<vector>#include<fstream>#include<unistd.h>using namespace std;#pragma pack(push, 1)static int stepSize=0;void handleDataUint(char *dataUnit, int size){  //得到数据之后,在这里进行拼包或者进行下一步处理等操作}int main(int argc, char **argv){         int socket_desc,rcv_size;      int err=-1;      socklen_t optlen;      struct sockaddr_in server;//定义服务器的相关参数      char server_reply[5000];      //Create socket      //下面的AF_INET也可以用PF_INET。AF_INET主要是用于互联网地址,而 PF_INET 是协议相关,通常是sockets和端口      socket_desc = socket(AF_INET , SOCK_STREAM , 0);//第二个参数是套接口的类型:SOCK_STREAM或SOCK_DGRAM。第三个参数设置为0。      if (socket_desc == -1)      {          printf("Could not create socket");      }      rcv_size = 4*640000;    /* 接收缓冲区大小为4*640K */      optlen = sizeof(rcv_size);      err = setsockopt(socket_desc,SOL_SOCKET,SO_RCVBUF, (char *)&rcv_size, optlen);//设置套接字,返回值为-1时则设置失败      if(err<0){              printf("设置接收缓冲区大小错误\n");      }      server.sin_addr.s_addr = inet_addr("192.168.10.2");//服务器IP地址      server.sin_family = AF_INET;//对应与socket,也可选PF_INET      server.sin_port = htons( 52404 );//端口号      if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)//用建立的socket尝试同设置好的服务器connect      {          perror("connect error:");          return 1;      }      printf("Connected");      char recbuff[800000];//接收的数据缓存大小,这是我自己设置的区域,为了存放报文      unsigned long buffsize;      int packetCount = 0;      buffsize=0;//该值标示当前缓存数据的大小,及下一帧数据存放的地址          while (1) {              int readSize = recv(socket_desc, server_reply , sizeof(server_reply) , 0);//从服务器接收数据,其中第三个参数为单次接收的数据大小//同上面的rcv_size区分开,上面的rcv_size是TCP/IP的机理,传输过程中,数据会暂时先存储在rcv_size里.//然后你recv再从rcv_size这个缓冲里取你设置的sizeof(serve_reply)的数据。其中readSize为recv的返回值,该值返回你实际接收到的数据大小,这点要注意。//接收到的数据放在server_reply[5000]里面              if(readSize < 0) {//实际接收到的数据为负,表示接收出错                  printf("recv failed");                  //should shut down connection and reconnect!                  assert(false);                  return 1;              }              else if (readSize == 0) {//表示无数据传输,接收到数据为0                  printf("readSize=0");                  break;              }              ++packetCount;//接收到包的次数加1              memcpy(recbuff + buffsize, server_reply, readSize);//memcpy为内存拷贝函数,将该次接收到的server_reply的数据拷贝到recbuff里//其中+buffsize,从recbuff头地址+buffsize的地址开始拷贝(第一次buffsize=0,及从头开始拷贝)拷贝的大小为readSize,即recv实际接收到的数据大小。              buffsize += readSize;//buffsize=buffsize+readSize,此时buffsize指向该次拷贝的数据大小的下一位。              const int packetHeadSize = 8;//定义控制域的数据大小为8个字节              static int expectedPacketSize = -1;//定义期望得到的数据包大小为-1,以此判断本次接收到的数据是否是数据头部              if (expectedPacketSize == -1 && buffsize >= packetHeadSize) {//接收到的数据比8个字节大,即包含了控制域及数据域,则进行下一步分析                  //start token must be 0x0628, otherwise reconnect!                  unsigned char t0 = recbuff[0];//取出接收数据的前两个字节                  unsigned char t1 = recbuff[1];                  assert(t0 == 0x28 && t1 == 0x06);//判断是否为0628H,是否符合启动字符条件,注意小端对齐                  if (!(t0 == 0x28 && t1 == 0x06))//如果不是0628H,则表示该次数据有误                  {                      return 1;                  }                  //find packet length!!                  unsigned short len = 0;//定义报文长度                  memcpy(&len, recbuff + 2, 2);//将接收数据的下第三第四个字节付给len,根据协议第三第四个字节存储的是该帧报文的长度                  expectedPacketSize = len + packetHeadSize;//则期望得到的数据包大小为报文长度加控制域长度              }              //get one whole packet!              if (expectedPacketSize != -1 && buffsize >= expectedPacketSize) {//当期望得到的数据包大小不是-1                  // 并且recbuff里接收到的数据大小已经大于所需要的数据大小,如果接收到的数据小于完整报文的长度,则继续接收                  //                  //下面为接收到的完整的一帧报文,定义了一个解包函数负责解包,从缓存数据的第9个字节开始取,取完整数据域长度的数据,即只取数据域的内容                  handleDataUint(recbuff + packetHeadSize, expectedPacketSize -packetHeadSize);//下面的memmove函数是内存移动函数,将下一帧报文移动到recbuff的起始处,覆盖掉已经取出的数据                  memmove(recbuff, recbuff + expectedPacketSize, buffsize - expectedPacketSize);                  buffsize -= expectedPacketSize;                  expectedPacketSize = -1;              }          }      return 0;}#pragma pack(pop)

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

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

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

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

0 0
原创粉丝点击