有段时间没有更新博客了,近来比较忙,没有顾上写博客。终于完成了一个大任务,有时间回顾一下这段时间的成果。这篇博客,先介绍和总结一下很久前的工作。TCP/IP接收数据拼包。由于时间太长很多东西记不清楚了,请见谅。
任务是某设备通过WIFI以TCP/IP的协议发送图像数据,数据按照规定的报文协议接收数据。
报文内容分为控制域(8个字节)与数据域(不定长),报文的启动字符为0628H占两个字节,接下来两个字节是报文长度(除去控制域本身之外的所有字节长度,因此加上启动字符在内的完整的报文为报文长度的值+8个字节)。控制域后面4个字节预留。数据域前2个字节为数据类型,接下来2个字节为数据内容长度,接下来2个字节为帧类型,接下来2个字节标志位标示是否有后续帧,然后是真正的图像数据内容(由于图像数据内容很大,一帧报文数据可能发不完,因此分多帧发送,后续帧标志位就标志某一帧图像数据是否发完)。
然后约定,所有数据类型按小端对齐(低字节在前。当然也可以约定大端对齐,高字节在前。)
好的,协议定好,接下来就开始发送数据和接收数据把。不过,发送数据的工作不在我这边,我只负责接收。不过,发送数据的工作跟接收数据的工作可以互相参考一下。整理一下我的任务,我需要通过TCP接收发过来的数据,识别出启动字符和报文长度,然后按报文长度的值接收报文。接收完一帧报文后,开始解包操作。由于一帧完整的图像可能分多帧报文发,所以,解包的时候需要注意是否有后续帧,数据是否完整了。
好,网上如何TCP接收数据的代码很多,先上代码。
[cpp] view plain copy #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++的拼包的代码。希望有用哈~
时间太长,很多别的东西记不住了,就先这样吧,我得去忙了。
