c/c++网络编程 对象的传输 以及 TCP粘包处理 解析

转载请标示文章出处:http://blog.csdn.net/luoti784600/article/details/12646405

基于TCP的网络编程中, 数据传输是基于连接的,所以当网络出现堵塞或者发送频率过高的时候,就会出现粘包的情况。

粘包就是并不是一个接收对应一个发送,有可能一个接收对应多个发送,也可能一个接收少于一个发送。

由于我们在网络编程中,经常以对象作为发送的单元,所以接受端必须对粘包做处理,还原原来的对象。


下图说明了接受端接收到数据的各种情况:


当然,接收到第一种情况是最理想的,也不须处理。本文针对2 3 4情况做处理。

算法解析:

   首先有一个对象用于保存上次未能处理的数据,和上次为处理数据的长度。

1.  将本次接收到的数据拼接到上一次未处理数据后面,为未处理数据。

2.  判断未处理数据长度是否大于包头,

     若小于包头,直接退出(包头保存长度信息) , 否则转3。

3. 根据包头判断对象大小是否大于未处理数据长度,若是转3, 否则保存未处理数据退出。

4. 截出第一个对象进行处理,剩下的数据重新保存到未处理对象,继续转2循环.

// TcpDataSplit.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NETPACK_SIZE	10000
#define MAX_DATA_SIZE			4086

/* 数据包头类型 */
struct NetDataHeader_t
{
	int nDataType;											//数据包类型,标识对应的对象类型
	int nDataSize;											//数据包中szData真实数据的长度
};

/*  数据包类型 */
struct NetDataBase_t
{
	NetDataHeader_t  dataHeader;			//数据包头
	char	 szData[MAX_DATA_SIZE];				//真实数据
};

/**
    其实NetDataBase_t是基础类型,由此我们可以延伸出很多子类型,
	所以我们要清楚,每个类型的长度是不一样的,不都是sizeof(NetDataBase_t),
	就是各个类型对象大小不一样,比如:
	在派生结构体中,NetDataPeople_t和NetDataSchool_t是两个各异的结构体,
	但他们都有相关的Header部分指明结构体类型和长度。
*/
struct NetDataPeople_t
{
	NetDataHeader_t dataHeader;	
	int		nAge;
	char    szName[10];
};

struct NetDataSchool_t
{
	NetDataHeader_t dataHeader;	
	char    szShoolName[20];
	char    szShoolAddress[30];
};

/**
    处理整理好的对象。
*/
bool HandleNetPack(NetDataHeader_t* pDataHeader);


bool TcpDataSplit(const char* szRecNetData, int nRecSize)
{
	/**
	    对于szLastSaveData, nRemainSize,为了简单,本例子只
		作为静态变量使用,因此只限于一个socket的数据接收,
		假如要同时处理多个socket数据,请放在对应容器里保存
	*/
	static char szLastSaveData[MAX_NETPACK_SIZE];
	static int nRemainSize = 0;
	static bool bFirst = true;

	if (bFirst)
	{
		memset(szLastSaveData, 0, sizeof(szLastSaveData));
		bFirst = false;
	}

	/* 本次接收到的数据拼接到上次数据 */
	memcpy( (char*)(szLastSaveData+nRemainSize), szRecNetData, nRecSize );
	nRemainSize = nRecSize + nRemainSize;

	/* 强制转换成NetDataPack指针 */
	NetDataHeader_t* pDataHead = (NetDataHeader_t*)szLastSaveData;

	/**
	   核心算法 
	*/
	while ( nRemainSize >sizeof(NetDataHeader_t) &&
				nRemainSize >= pDataHead->nDataSize +sizeof(NetDataHeader_t) )
	{
			HandleNetPack(pDataHead);
			int  nRecObjectSize = sizeof(NetDataHeader_t) + pDataHead->nDataSize;		//本次收到对象的大小
			nRemainSize -= nRecObjectSize ;				
			pDataHead = (NetDataHeader_t*)( (char*)pDataHead + nRecObjectSize );		//移动下一个对象头
	}
	
	/* 余下数据未能组成一个对象,先保存起来 */
	if (szLastSaveData != (char*)pDataHead)
	{
		memmove(szLastSaveData, (char*)pDataHead, nRemainSize);
		memset( (char*)( szLastSaveData+nRemainSize), 0, sizeof(szLastSaveData)-nRemainSize );
	}
	
	return true;
}


/**
    处理整理好的对象。
*/
bool HandleNetPack(NetDataHeader_t* pDataHeader)
{
	//处理数据包
	if  (pDataHeader->nDataType == 1)
	{
		NetDataPeople_t* pPeople = (NetDataPeople_t*)pDataHeader;
		printf("收到People对象,Age:%d, Name:%s\n", pPeople->nAge, pPeople->szName);
	}
	else if (pDataHeader->nDataType == 2)
	{
		NetDataSchool_t* pSchool = (NetDataSchool_t*)pDataHeader;
		printf("收到School对象,SchoolName:%s, SchoolAddress:%s\n", pSchool->szShoolName, pSchool->szShoolAddress);
	}

	return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
	/* 本例子以两个对象作为接收到的数据 */
	NetDataPeople_t  people;
	people.dataHeader.nDataSize = sizeof(people) - sizeof(NetDataHeader_t);
	people.dataHeader.nDataType = 1;
	people.nAge = 20;
	sprintf(people.szName, "Jim");		//real data

	NetDataSchool_t  school;
	school.dataHeader.nDataSize = sizeof(school) - sizeof(NetDataHeader_t);
	school.dataHeader.nDataType = 2;
	sprintf(school.szShoolName, "清华大学");		//real data
	sprintf(school.szShoolAddress, "北京市北京路");		//real data

	/* 将两个对象数据合并到一个地址里面以便重现粘包 */
	char szSendData[sizeof(people)+sizeof(school)];
	memcpy(szSendData,  (char*)&people,  sizeof(people));
	memcpy(szSendData+sizeof(people),  (char*)&school,  sizeof(school));

	//这里进行收数据操作,这里省略。。。

	/**
	    特意设置粘包:
		1.第一次只发送3个字节,还不足以构建包头
		2.第二次发送10个字节,总共13个,但第一个对象大小是8+14=18;因此第一个对象people还没收满
		3.第三次发送剩下的全部,第一个对象剩下的部分与第二个对象粘在一起,验证处理
	*/
	TcpDataSplit((char*)szSendData, 3);
	TcpDataSplit((char*)szSendData+3,  10);
	TcpDataSplit((char*)szSendData+13,  sizeof(szSendData)-13);

	getchar();
	return 0;
}



  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值