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++的拼包的代码。希望有用哈~
时间太长,很多别的东西记不住了,就先这样吧,我得去忙了。
- C++中TCP/IP按约定报文协议接收数据完成拼包
- C++中TCP/IP按约定报文协议接收数据完成拼包
- tcp-ip : tcp接收数据
- DB11 TCP数据协议拆包接收主要方法
- c#中tcp协议服务器同时接收客户端的数据
- TCP/IP 之TCP协议(1):概述和报文格式
- 《LwIP协议栈源码详解——TCP/IP协议的实现》以太网数据接收
- IP数据报、TCP报文、UDP报文格式
- Java TCP/IP协议学习笔记:分组报文和协议
- TCP/IP协议、报文格式、三次握手协议
- TCP协议中报文段详解
- TCP协议发送数据:TCP 协议接收端(服务端)数据,
- TCP/IP第四层--传输层TCP数据报文详解
- TCP/IP第四层--传输层TCP数据报文详解
- Linux TCP/IP 协议栈源码分析 - 数据 发送/接收 流程图
- TCP IP详解(6)Internet控制报文协议
- TCP/IP协议学习备忘之ICMP报文重定向
- TCP/IP(6)ICMP Internet控制报文协议
- Linux 下Crontab 里的命令无法执行的问题
- 系统架构札记
- CodeForces 631 C.Report(单调栈)
- Java中的Arrays类使用详解
- 自然语言处理——Pattern(pattern.vector)
- C++中TCP/IP按约定报文协议接收数据完成拼包
- 关于submit与异步提交的区别
- PHP笔记
- static关键字
- vue中如何动态的绑定图片,vue中通过data返回图片路径
- IE兼容性处理实例
- Java入侵检测系统(一)
- AM335X 添加 SPI 设备(以添加 spi flash 为例)
- Python学习笔记1——安装篇