一、网络传输struct类型数据
在网络通讯过程中往往涉及一些有关联的参数传递,例如结构体之类的。对于结构体其实方法挺简单,由于结构体对象在内存中分配的空间都是连续的,所以可以将整个结构体直接转化成字符串发送,到了接收方再将这个字符串还原成结构体就可以了。
网络传输struct数据的约束有两个:
约束一、就是结构体的大小必须是固定的,不能含有可变大小数据,例如CString、string之类的数据。换句话说,结构体所包含的数据必须是C++基本类型数据以及这些基本类型数据所形成固定大小的数组。
约束二、就是传接两方结构体定义必须一模一样,包括数据声明次序。如果要发送的结构体包含“#pragma pack (n)”之类的东西(具体可看http://blog.csdn.net/21aspnet/article/details/6730124),则接收方在定义此结构体时也要使用“#pragma pack (n)”声明。传接之所以能够成功是因为结构体数据的内存区域连续性所保证的
本来嘛在C/C++中所有数据究其本质都是字节类型,只是在表现时各自不同罢了,所以只要能找到合适的转换为字节类型数据的途径就OK了。而字节类型和char类型一样都是一个字节长度,所以问题又等同于找一条合适途径,将信息转换为固定长度char数组类型。
下面举例说明
1、结构体数据全部都是数组
typedef struct _tag_user_info_
{
char cUserID[20];
char cUserSex[10];
char cUserName[18];
char cUserNativePlace[50];
} UserData;
//发送方:创建一个对象并初始化各个参数,然后发送。
UserData sendUser;
memcpy ( sendUser.cUserID, "412902198312120311",sizeof("412902198312120311"));
memcpy ( sendUser.cUserSex, "male",sizeof("male"));
memcpy ( sendUser.cUserName, "JianYa.Lee",sizeof("JianYa.Lee"));
memcpy (
sendUser.cUserNativePlace,
"Asia. P.R.C .HeNan-DengZhouShi",
sizeof("Asia. P.R.C.HeNan-DengZhouShi")
);
send ( m_oSendSocket, (char*)&sendUser,sizeof(UserData), 0 );
需要注意的地方:send函数的第三个参数,也就是发送数据长度必需是结构体的大小,这样发送方就已经将这个sendUser对象以字符串的形式发送出去了,剩下的工作就由接收方来完成了
接收方:首先也必须有UserData这个结构体类型定义。其次,首先定义一个充分大char类型数组,用于接收网络发送数据。然后将接收到的数据用memcpy函数完成强转即可。
// 定义的char数组足够大
charcRcvBuffer[1024] = {0};
// 定义一个UserData对象, 用于容纳转换信息
UserData recvUser;
recv( m_RcvSocket, cRcvBuffer, sizeof(cRcvBuffer),0 );
// 强转, 请注意sizeof的内容
memcpy( &recvUser, cRcvBuffer, sizeof(UserData) );
这样得到的recvUser对象里的数据与sendUser相同了。
2、结构体数据没有包含数组
// 发送方:创建struct结构体
typedef struct _tag_other_data_
{
INT32 nValue;
char cValue;
bool blValue;
float fValue;
double dValue;
short sValue;
} SecondData;
// 定义结构体对象,并初始化
SecondData oScdData;
// 初始化数据内容
oScdData.blValue = true;
oScdData.cValue = 'h';
oScdData.dValue = 0.1234567;
oScdData.fValue =3.14159f;
oScdData.nValue = 123456;
oScdData.sValue = 0x1024;
// 注意sizeof内容
send(m_oSendSocket, (char*)&oScdData,sizeof(SecondData), 0);
接收方:首先定义SecondData结构体,数据类型、声明次序需完全一样;其次声明一个足够大的char类型数组;最后强转。
// 定义char类型数组
charcRcvBuffer[1024] = {0};
SecondData oRcvData;
// 注意sizeof内容
recv(m_oRcvSocket, cRcvBuffer, sizeof(cRcvBuffer), 0);
// 强制转换, 注意sizeof内容
memcpy(&oRcvData, cRcvBuffer, sizeof(SecondData));
接收方:首先定义SecondData结构体,数据类型、声明次序需完全一样;其次声明一个足够大的char类型数组;最后强转。
// 定义char类型数组
charcRcvBuffer[1024] = {0};
SecondData oRcvData;
// 注意sizeof内容
recv(m_oRcvSocket, cRcvBuffer, sizeof(cRcvBuffer), 0);
// 强制转换, 注意sizeof内容
memcpy(&oRcvData, cRcvBuffer, sizeof(SecondData));
3、多个结构体数据的传接
发送方:
struct structA
{
INT32 nValue;
char cValue;
};
struct structB
{
bool blValue;
short sValue;
};
struct structC
{
float fValue;
char cValue;
unsigned long unValue;
};
// 三个结构体定义各不相同,现在要给它们建立一个统一的传接模式,此时可以考虑使用联合union,外加一个类型指示。
typedef struct _tag_unification_data_
{
// 用于指示结构体类型, 比如IS_STRUCT_A就代表structA、
// IS_STRUCT_B就代表struct_B、
// IS_STRUCT_C就代表structC
INT32 nStructType;
// 每次传送的是三种struct中的一种
union
{
struct structA aData;
struct structB bData;
struct structC cData;
};
} PACKETDATA;
// 结构体类型标识
enum{IS_STRUCT_A, IS_STRUCT_B, IS_STRUCT_C};
// 定义结构体对象,并初始化
PACKETDATA oMyData;
// 发送structA类型数据
oMyData.nStructType = IS_STRUCT_A;
oMyData.aData.cValue = 'g';
oMyData.aData.nValue = 130;
// 注意后面的sizeof内容
send(oSendSocket, (char*)&oMyData,sizeof(PACKETDATA), 0);
接收方:
首先必需由PACKETDATA一样的定义;
其次,定义一个足够大的char数组;
最后完成强转,在使用的时候进行具体类型判断即可。
// 定义char类型数组
charcRcvBuffer[1024] = {0};
PACKETDATA oRcvData;
// 注意sizeof内容
recv(m_oRcvSocket, cRcvBuffer, sizeof(cRcvBuffer), 0);
// 强制转换, 注意sizeof内容
memcpy(&oRcvData, cRcvBuffer, sizeof(PACKETDATA));
// 在使用时进行具体类型判断
switch (oRcvData.nStructType)
{
caseIS_STRUCT_A:
// structA类型数据
break;
caseIS_STRUCT_B:
// structB类型数据
break;
caseIS_STRUCT_C:
// structC类型数据
break;
}
二、TCP粘包分包处理(1)
基于TCP的网络编程中, 数据传输是基于连接的,所以当网络出现堵塞或者发送频率过高的时候,就会出现粘包的情况。
粘包就是并不是一个接收对应一个发送,有可能一个接收对应多个发送,也可能一个接收少于一个发送。
由于我们在网络编程中,经常以对象作为发送的单元,所以接受端必须对粘包做处理,还原原来的对象。
下图说明了接受端接收到数据的各种情况:
当然,接收到第一种情况是最理想的,也不须处理。本文针对2 3 4情况做处理。
算法解析:
首先有一个对象用于保存上次未能处理的数据,和上次为处理数据的长度。
1. 将本次接收到的数据拼接到上一次未处理数据后面,为未处理数据。
2. 判断未处理数据长度是否大于包头,
若小于包头,直接退出(包头保存长度信息) , 否则转3。
3. 根据包头判断对象大小是否大于未处理数据长度,若是转3, 否则保存未处理数据退出。
4. 截出第一个对象进行处理,剩下的数据重新保存到未处理对象,继续转2循环.