TCP粘包,因为网络数据并非一次性到达实际情况会出现很多种情况,很难保证数据完整性,于是包网络数据缓存起来,当数据足以处理的时候进行处理,可以一定程度上解决粘包的问题。当然作为一种连续存储数据,要区分所以就需要定义包头。这将额外消耗一点网络带宽,不过这点带宽,根本不算什么,因为是字节级别的一个包。
通常用网络接受数据会采用异步处理。当数据接收后,需要对数据进行一系列的处理,boost提供的CircleBuffer是一种不错的选择和解决思想。但是借鉴于这种思想,自己编写了一种同样可以再使用的buffer。
-------------------与CircleBuffer 不同,这个缓存更适合做网络缓存的
【同时我真心希望,有人能提供修改意见,我将持续更新这个buffer的不完美支持,并随时开源】
先说说CircleBuffer,CircleBuffer在使用起来的时候一直可以push数据,并且始终报保持大小为定义的大小,好处是可以随时随地的插值。
但实际上CircleBuffer的内存排布依然是连续的直线形状。
这将会造成一个问题,就是byte[] 数组 转 对象的问题。我们知道class/struct 可以直接转化成char的数组,直接通过socket.send等发送方法直接发送给其他链接。其他链接收到后可以通过直接把char[]转换成class/struct对象,从而进行相关业务逻辑的操作。
但是在这里如果circlebuffer 达到了临界值,这个时候通过获取begin指针在转的时候就不行了,因为数据其实在一头一尾。
数据块1,2,3其实并没有按照顺序进行排列。因此需要编写额外的代码,以补充circlebuffer 在这方面的表现不足【可能是我的专断,或许circlebuffer 有更好的办法,只是本人才疏学浅,没有发现】
不过既然这样,我就索性自己实现了一个网络缓冲区。
其中使用到了std::array
还是讲一下思路:
1.声明一块内存区,用来缓存网络数据,定义mlen长度为0,索引为0
2.添加新数据的时候
2.1.正常情况:
2.2但是随着数据的迁移或者增多,会遇到数据已经在尾部的瓶颈。如图:
所以此时我采用的把法就是把原有数据拷贝,这就依然保障了数据的连续性,转换数据的时候就再也不用担心数据的不连续行而转换成其他错误的数据包。
3.缓存区肯定只是缓存数据的,因此肯定会存在数据“消费”的情况[虽然提到消费,但并没使用生产-消费者模式],于是出现了和Circlebuffer一样需要“移动”起始指针的地方
好了基本原理就是这样的。但是由于实际使用过程中我们肯定会出现对数据包的验证,这些其实都是重复制造轮子的过程,于是我就把数据头和事件都封装好了。
【再次希望有网友能帮忙提出修改意见,让更多的人使用到更好的轮子!】
/****************************************
// 循环Buffer,用于缓冲网络数据
// LoadBuffer<int> 建议如果是网络协议建议缓冲区还是声明在1024以上吧
// 版本:1.0
****************************************/
#ifndef _RTS_REGENTBUFFER_H_
#define _RTS_REGENTBUFFER_H_
// 定义当前版本
#define RTSNETVersion 1
#include <array>
#include <functional>
#include <queue>
// 网络接收的类型
typedef unsigned char RTSBRByte;
struct RTSHead;
class RTSNetPack;
// 收到完成包的时候的回调函数
typedef std::function< void(RTSNetPack& ) > ReciveNetPackCallBack;
// 类型头
struct RTSHead
{
char head[2];
int version;
int MainArg;
int DataLen;
char end[2];
RTSBRByte* Datas;
};
const int RTSHeadSz = sizeof(RTSHead);
// 定义给客户端使用的包,为了让客户不用释放文件采用封装
class RTSNetPack
{
public:
// 创建一个可以使用的包
RTSNetPack( std::shared_ptr< std::vector<RTSBRByte> > rtsVec )
{
this->mdataVec = rtsVec;
RTSHead *p = (RTSHead*)&(*mdataVec->begin());
std::shared_ptr< RTSHead > head( new RTSHead() );
RTSHead *ph = head.get();
ph = p;
mHead = head;
// 判断包长度是否为0,如果为0就修改为0
if( mHead->nLen > 0 )
{
head->Datas = (&(*mdataVec->begin())+RTSHeadSz);
}else
{
head->Datas = 0;
}
}
~RTSNetPack()
{
}
public:
// 获取数据
std::shared_ptr< RTSHead >GetData()
{
return mHead;
}
private:
std::shared_ptr< std::vector<RTSBRByte> > mdataVec;
std::shared_ptr< RTSHead > mHead;
};
//const int RTSHeadSz = sizeof(RTSHead);
// 网络协议头工厂
class RTSHeadFactory
{
private:
RTSHeadFactory(){};
~RTSHeadFactory(){};
RTSHeadFactory(const RTSHeadFactory&){};
public:
// 创建网络包头
static RTSHead CreateNetHead(int arg,int len = 0)
{
RTSHead head;
head.head[0] = head.head[1] = 'E';
head.end[0] = head.end[1] = 'F';
head.MainArg = arg;
head.DataLen = len;
head.version = RTSNETVersion;
head.Datas = 0;
return head;
}
// 判断是否是包含一个
static bool IsBufferHead( RTSBRByte *datas,int len )
{
if( len < RTSHeadSz )return false;
RTSHead *pData = (RTSHead*)datas;
if( pData->head[0] != 'E' )return false;
if( pData->head[1] != 'E' )return false;
if( pData->MainArg < -1 )return false;
if( pData->MainArg > 9999 )return false;
if( pData->DataLen < 0 )return false;
if( pData->DataLen > 9999999 )return false;
if( pData->version != RTSNETVersion )return false;
if( pData->end[0] != 'F' )return false;
if( pData->end[1] != 'F' )return false;
return true;
}
// 从一个内存中获取包头
static RTSHead* GetRTSHead(RTSBRByte *datas,int len )
{
if(IsBufferHead(datas,len))
{
RTSHead *p = (RTSHead*)datas;
return p;
}else
{
return 0;
}
}
};
template<int Len>
class LoadBuffer
{
public:
explicit LoadBuffer(void)
{
mlen = 0;
mindex = 0;
mReciveCallBack = nullptr;
};
~LoadBuffer(void)
{
};
public:
// 插入单独一个数据
bool PushData(RTSBRByte target)
{
// 超过容器最大限度将无法插入
if( 1 + mlen > Len )return false;
if( WillFull(1) )
{
Rehead();
}
// 进行插入
mbuffer[mindex + mlen] = target;
mlen++;
PackCheck();
return true;
}
// 插入一堆数据
bool PushData( RTSBRByte* target,int addcount )
{
// 这种情况表示无论如何都无法插入缓冲区,因为缓冲区太小
if( addcount + mlen > Len )return false;
if( WillFull(addcount) )
{
Rehead();
}
// 进行插入
int _len = addcount;
int counter = 0;
while (_len--)
{
mbuffer[mindex + mlen + counter] = target[counter];
counter++;
}
mlen = mlen + addcount;
PackCheck();
return true;
}
// 设置满足条件的时候的调用事件
void SetReciveCallBack(ReciveNetPackCallBack val) { mReciveCallBack = val; }
// 复制所有有效的值到头缓存,比较消耗时间
void Rehead()
{
int llen = mlen;
int index = 0;
while (llen--)
{
mbuffer[index] = mbuffer[mindex+index];
index++;
}
mindex = 0;
}
// 重置,true缺省:表示重置并重设置所有值为0,false:进修改起点,而不用清空buffer的值.推荐使用false
void Reset(bool withSet = true)
{
int mlen = 0;
int mindex = 0;
// 并且执行清空操作
if(withSet)
{
int counter = Len;
while( counter-- )mbuffer[counter] = '\0';
}
}
private:
// 判断是否将要插满缓冲区
bool inline WillFull( int addcount )
{
bool willFull = (mindex + mlen + addcount) > Len ? true:false;
return willFull;
}
// 包体的检查机制,当数据发生增长的时候将进行检查,如果查询到缓冲中有满足包头条件的数据将对该数据进行回调
void PackCheck()
{
// 1.当前数据长度比包头小则肯定不满足条件
if( mlen < RTSHeadSz )return;
// 2.若长度不小于包头长度,就先判断是否是一个正确的包头
RTSBRByte *head = (mbuffer.data() + mindex);
if( RTSHeadFactory::IsBufferHead(head,mlen) )
{
RTSHead *h = RTSHeadFactory::GetRTSHead(head,mlen);
// 2.1.1 虽然是个正确的包头,但是携带的数据长度小于包要求的长度任然需要等待下一次数据的添加在进行检查
if( mlen - RTSHeadSz < h->DataLen )
{
return;
}else
{
// 2.2.2 表示满足包头的条件就将结果放入队列中进行解析
int totalCount = RTSHeadSz + h->DataLen;
// 赋值给智能指针
std::shared_ptr< std::vector<RTSBRByte> >bufferData(new std::vector< RTSBRByte >(head,head + totalCount));
bufferData->push_back('\0');
// 创建包
RTSNetPack netPack(bufferData);
// 进行事件操作
if( nullptr != mReciveCallBack )
{
mReciveCallBack(netPack);
}
// 2.2.3 同时,计算出新len的长度和偏移值,进行再次判断如果仍然可以凑齐一个包头的长度,继续递归调用
mlen = mlen - totalCount;
mindex = (mindex + totalCount) == Len ? 0 : mindex + totalCount;
if( mlen < RTSHeadSz )
{
return;
}else
{
// 继续递归调用
PackCheck();
}
}
}else
{
// 2.2 不是一个正确的包就将当前缓数据移动至下一个点位进行检查
MoveToNext();
PackCheck(); // 递归调用进行数据的再一次检查知道小于包头
}
}
// 移动到下个点位
void MoveToNext()
{
// 长度为0 不移动
if( mlen == 0 )return;
--mlen;
// 索引为最后一个移动的下一个位置index应该变成0位
mindex = mindex == (Len - 1) ? 0:(mindex + 1);
}
// 禁止拷贝
LoadBuffer(const LoadBuffer&){};
// 缓冲区域
std::array< RTSBRByte,Len > mbuffer;
// 当前缓冲长度
int mlen;
// 当前使用的缓冲索引
int mindex;
// 检查满足包头条件的时候进行回调使用
ReciveNetPackCallBack mReciveCallBack;
};
#endif
使用方法:
#include <memory>
#include <iostream>
#include "RTSRegentBuffer.h"
#include <vector>
void main()
{
// 1.定义了一个缓冲区为10K的缓冲区,用于缓冲数据
std::shared_ptr< Loadbuffer<10240> > rb( new Loadbuffer<10240> () );
RTSHead head = RTSHeadFactory::CreateNetHead(0,1);
// 2.设置满足条件的事件回调
rb->SetReciveCallBack([](RTSNetPack &buffer )
{
// 具体要处理什么事件在这里,这里是同步,为了使用异步,请自行采用多线程
auto d = buffer.GetData();
std::cout<<(char*)d->Datas<<std::endl;
});
// 3.网络事件收到数据,这里程序就会自行的判断当前缓冲数据是否满足执行一条事件
// 从此就不用根据收到数据之后还要在重复的写网络包的这种逻辑了。
rb->PushData((RTSBRByte*)&head,sizeof(head));
rb->PushData('a');
}
虽然这个轮子不是什么STL、Boost等大场出品,但仍然不失为一个好的库。
如果是使用C#、或者java等其他语言的同学,仍然可以借助这种思想来实现一个网络缓冲区。