结构体传输 & TCP粘包处理

一、网络传输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循环.

[cpp]  view plain   copy
  1. // TcpDataSplit.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <stdio.h>  
  6. #include <stdlib.h>  
  7. #include <string.h>  
  8.   
  9. #define MAX_NETPACK_SIZE    10000  
  10. #define MAX_DATA_SIZE           4086  
  11.   
  12. /* 数据包头类型 */  
  13. struct NetDataHeader_t  
  14. {  
  15.     int nDataType;                                          //数据包类型,标识对应的对象类型  
  16.     int nDataSize;                                          //数据包中szData真实数据的长度  
  17. };  
  18.   
  • 9
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
是的,结构可以通过 TCP Socket 进行通信。TCP传输控制协议)提供了可靠的、面向连接的通信服务,可以用于在网络中传输数据。在使用 TCP Socket 进行通信时,可以将结构作为数据的载进行传输。 以下是一个简单的示例,展示了如何在客户端和服务器之间通过 TCP Socket 传输结构的数据: 客户端: ```c #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;unistd.h&gt; #include &lt;arpa/inet.h&gt; struct Person { char name[50]; int age; }; int main() { // 创建 TCP Socket int clientSocket = socket(AF_INET, SOCK_STREAM, 0); // 设置服务器地址 struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(12345); serverAddress.sin_addr.s_addr = inet_addr(&quot;服务器IP地址&quot;); // 连接到服务器 connect(clientSocket, (struct sockaddr*)&amp;serverAddress, sizeof(serverAddress)); // 创建结构并赋值 struct Person person; strcpy(person.name, &quot;John Doe&quot;); person.age = 25; // 发送结构数据 send(clientSocket, &amp;person, sizeof(person), 0); // 关闭 Socket close(clientSocket); return 0; } ``` 服务器端: ```c #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;unistd.h&gt; #include &lt;sys/socket.h&gt; #include &lt;arpa/inet.h&gt; struct Person { char name[50]; int age; }; int main() { // 创建 TCP Socket int serverSocket = socket(AF_INET, SOCK_STREAM, 0); // 设置服务器地址 struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(12345); serverAddress.sin_addr.s_addr = INADDR_ANY; // 绑定 Socket bind(serverSocket, (struct sockaddr*)&amp;serverAddress, sizeof(serverAddress)); // 监听连接请求 listen(serverSocket, 5); // 接受客户端连接 struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); int clientSocket = accept(serverSocket, (struct sockaddr*)&amp;clientAddress, &amp;clientAddressLength); // 接收结构数据 struct Person person; recv(clientSocket, &amp;person, sizeof(person), 0); // 输出结构数据 printf(&quot;Name: %s\n&quot;, person.name); printf(&quot;Age: %d\n&quot;, person.age); // 关闭 Socket close(clientSocket); close(serverSocket); return 0; } ``` 在这个示例中,客户端创建一个 TCP Socket,并连接到服务器。然后,客户端创建一个结构 `Person`,并将其发送到服务器端。服务器端创建一个 TCP Socket,并绑定到本地 IP 地址和端口上。它监听客户端的连接请求,并接受客户端的连接。一旦连接建立,服务器端接收来自客户端的结构数据,并将其输出到控制台。 当然,你需要将示例中的 IP 地址替换为实际的服务器 IP 地址,并确保客户端和服务器在相同的网络中可达。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值