http://www.zixue7.com/article-2172-1.html
http://www.cppblog.com/tx7do/archive/2011/05/07/145865.html
http://www.cnblogs.com/jiangtong/archive/2012/03/22/2411985.html
http://www.cnblogs.com/jiangtong/archive/2012/03/22/2411985.html
https://www.cnblogs.com/my_life/articles/5069522.html
TCP为什么需要进行封包解包?
TCP采用字节流的方式,即以字节为单位传输字节序列。那么,我们recv到的就是一串毫无规则的字节流。如果要让这无规则的字节流有规则,那么,就需要我们去定义一个规则。那便是所谓的“封包规则”。
封包结构是怎么样的?
封包就像是信,信是由:信封、信内容。两部分组成。而网络封包也是由两部分组成:包头、数据。包头域是定长的,数据域是不定长的。包头必然包含两个信息:操作码、包长度。包头可能还包含别的信息,这个呢就要视乎情况去定了。操作码是该网络数据包的标识符,这就和UI里面的事件ID什么的差不多。其中,操作码有的只有一级,有的则有两级甚至多级操作码,这个的设计也要看情况去了,不过,这些底层的东西,定好了,基本就不能动了,就像房子都砌起来了,再去动地基,那就欧也了。
以下是网络数据包的伪代码:
struct NetPacket
{
包头;
数据;
};以下是包头的伪代码:
struct NetPacketHeader
{
操作码;
包长度;
};
收包中存在的一个问题(粘包,半包)
在现实的网络情况中,网络传输往往是不可靠的,因此会有丢包之类的情况发生,对此,TCP相应的有一个重传的机制。对于接收者来说,它接收到的数据流中的数据有可能不是完整的数据包,或是只有一部分,或是粘着别的数据包,因此,接收者还需要对接收到的数据流的数据进行分包。
======================
将数据能够在TCP中进行传输的两种方法
1.直接拷贝struct就可以了;
2.序列化。
拷贝Struct存在的问题
1.不能应付可变长类型的数据,比如STL中的那些容器,他们的长度都是不确定的。当然,STL的容器归根到底就是一个class;
2.内存对齐的问题,Windows默认的对齐是4字节,如果不去刻意关闭掉对齐的话,那么可能会多出不少没必要的字节数,有时候,这个损耗是客观的。但是如果关闭了,内存拷贝又会慢一些,内存IO相对于网络IO来说,速度是快的,略微的增加内存IO的压力来调优网络IO是可行的。
序列化是怎么序列化的?
其实很简单,就是按位拷贝。在这里,我们使用一个uint8类型的变长数组作为一个容器。假设我们这里有一个uint16类型的数据,那么我们就把它拷贝进去uint8的数组里面,那么它就占了两个元素,如果是uint32,则这个数据占了4个元素位。它的原理是非常的简单的。至于具体请参考下面代码里面的ByteBuffer::append()方法。而那些class神马的,只要我们序列化的顺序和反序列化的顺序是配对的,我们就可以按照这个顺序进行序列化和反序列化了。这个在BytBuffer里面已经默认支持了常用的几个STL容器(vector,list等)。
类型定义
#if defined(_MSC_VER)
//
// Windows/Visual C++
//
typedef signed __int8 int8;
typedef unsigned __int8 uint8;
typedef signed __int16 int16;
typedef unsigned __int16 uint16;
typedef signed __int32 int32;
typedef unsigned __int32 uint32;
typedef signed __int64 int64;
typedef unsigned __int64 uint64;
#endif
有的类型的长度会因硬件或者操作系统而异,如果直接使用c++关键字中的类型定义可能会出现问题。因此,需要自己定义以上这样的类型。利用宏去适配各个操作系统或者硬件平台。
ByteBuffer的代码
//////////////////////////////////////////////////////////////////////////
/// 字节流缓冲类,可以进行序列化和解序列化操作,并且可以缓冲字节流数据。
//////////////////////////////////////////////////////////////////////////
class ByteBuffer
{
public:
const static size_t DEFAULT_SIZE = 0x1000;
ByteBuffer()
: mReadPos(0)
, mWritePos(0)
{
mStorage.reserve(DEFAULT_SIZE);
}
ByteBuffer(size_t res)
: mReadPos(0)
, mWritePos(0)
{
mStorage.reserve(res);
}
ByteBuffer(const ByteBuffer &buf)
: mReadPos(buf.mReadPos)
, mWritePos(buf.mWritePos)
, mStorage(buf.mStorage)
{}
//////////////////////////////////////////////////////////////////////////
public:
void clear()
{
mStorage.clear();
mReadPos = mWritePos = 0;
}
template <typename T>
void append(T value)
{
append((uint8*)&value, sizeof(value));
}
template <typename T>
void put(size_t pos, T value)
{
put(pos, (uint8*)&value, sizeof(value));
}
//////////////////////////////////////////////////////////////////////////
public:
ByteBuffer& operator<<(bool value)
{
append<char>((char)value);
return *this;
}
ByteBuffer& operator<<(uint8 value)
{
append<uint8>(value);
return *this;
}
ByteBuffer& operator<<(uint16 value)
{
append<uint16>(value);
return *this;
}
ByteBuffer& operator<<(uint32 value)
{
append<uint32>(value);
return *this;
}
ByteBuffer& operator<<(uint64 value)
{
append<uint64>(value);
return *this;
}
ByteBuffer& operator<<(int8 value)
{
append<int8>(value);
return *this;
}
ByteBuffer& operator<<(int16 value)
{
append<int16>(value);
return *this;
}
ByteBuffer& operator<<(int32 value)
{
append<int32>(value);
return *this;
}
ByteBuffer& operator<<(int64 value)
{
append<int64>(value);
return *this;
}
ByteBuffer& operator<<(float value)
{
append<float>(value);
return *this;
}
ByteBuffer& operator<<(double value)
{
append<double>(value);
return *this;
}
ByteBuffer& operator<<(time_t value)
{
append<time_t>(value);
return *this;
}
ByteBuffer& operator<<(const std::string& value)
{
append((uint8 const *)value.c_str(), value.length());
append((uint8)0);
return *this;
}
ByteBuffer& operator<<(const char* str)
{
append( (uint8 const *)str, str ? strlen(str) : 0);
append((uint8)0);
return *this;
}
//////////////////////////////////////////////////////////////////////////
public:
ByteBuffer& operator>>(bool& value)
{
value = read<char>() > 0 ? true : false;
return *this;
}
ByteBuffer& operator>>(uint8& value)
{
value = read<uint8>();
return *this;
}
ByteBuffer& operator>>(uint16& value)
{
value = read<uint16>();
return *this;
}
ByteBuffer& operator>>(uint32& value)
{
value = read<uint32>();
return *this;
}
ByteBuffer& operator>>(uint64& value)
{
value = read<uint64>();
return *this;
}
ByteBuffer& operator>>(int8& value)
{
value = read<int8>();
return *this;
}
ByteBuffer& operator>>(int16& value)
{
value = read<int16>();
return *this;
}
ByteBuffer& operator>>(int32& value)
{
value = read<int32>();
return *this;
}
ByteBuffer& operator>>(int64& value)
{
value = read<int64>();
return *this;
}
ByteBuffer& operator>>(float &value)
{
value = read<float>();
return *this;
}
ByteBuffer& operator>>(double &value)
{
value = read<double>();
return *this;
}
ByteBuffer& operator>>(time_t& value)
{
value = read<time_t>();
return *this;
}
ByteBuffer& operator>>(std::string& value)
{
value.clear();
while (rpos() < size())
{
char c = read<char>();
if (c == 0)
break;
}
value += c;
}
return *this;
}
ByteBuffer& operator>>(char value[])
{
std::string strValue;
strValue.clear();
while (rpos() < size())
{
char c = read<char>();
if (c == 0)
{
break;
}
strValue += c;
}
strncpy(value, strValue.c_str(), strValue.size());
return *this;
}
//////////////////////////////////////////////////////////////////////////
public:
uint8 operator[](size_t pos)
{
return read<uint8>(pos);
}
size_t rpos() const
{
return mReadPos;
};
size_t rpos(size_t rpos_)
{
mReadPos = rpos_;
return mReadPos;
};
size_t wpos() const
{
return mWritePos;
}
size_t wpos(size_t wpos_)
{
mWritePos = wpos_;
return mWritePos;
}
template <typename T> T read()
{
T r = read<T>(mReadPos);
mReadPos += sizeof(T);
return r;
};
template <typename T> T read(size_t pos) const
{
assert(pos + sizeof(T) <= size() || PrintPosError(false,pos,sizeof(T)));
return *((T const*)&mStorage[pos]);
}
void read(uint8 *dest, size_t len)
{
assert(mReadPos + len <= size() || PrintPosError(false, mReadPos,len));
memcpy(dest, &mStorage[mReadPos], len);
mReadPos += len;
}
const uint8* contents() const { return &mStorage[mReadPos]; }
size_t size() const { return mStorage.size(); }
bool empty() const { return mStorage.empty(); }
void resize(size_t _NewSize)
{
mStorage.resize(_NewSize);
mReadPos = 0;
mWritePos = size();
};
void reserve(size_t _Size)
{
if (_Size > size()) mStorage.reserve(_Size);
};
void append(const std::string& str)
{
append((uint8 const*)str.c_str(), str.size() + 1);
}
void append(const char *src, size_t cnt)
{
return append((const uint8 *)src, cnt);
}
void append(const uint8 *src, size_t cnt)
{
if (!cnt) return;
assert(size() < 10000000);
if (mStorage.size() < mWritePos + cnt)
{
mStorage.resize(mWritePos + cnt);
}
memcpy(&mStorage[mWritePos], src, cnt);
mWritePos += cnt;
}
void append(const ByteBuffer& buffer)
{
if (buffer.size()) append(buffer.contents(),buffer.size());
}
void put(size_t pos, const uint8 *src, size_t cnt)
{
assert(pos + cnt <= size() || PrintPosError(true,pos,cnt));
memcpy(&mStorage[pos], src, cnt);
}
//////////////////////////////////////////////////////////////////////////
public:
void print_storage()
{
}
void textlike()
{
}
void hexlike()
{
}
bool PrintPosError(bool add, size_t pos, size_t esize) const
{
printf("ERROR: Attempt %s in ByteBuffer (pos: %u size: %u) value with size: %u",(add ? "put" : "get"), pos, size(), esize);
return false;
}
protected:
size_t mReadPos;
size_t mWritePos;
std::vector<uint8> mStorage;
};

//////////////////////////////////////////////////////////////////////////
// std::vector
//////////////////////////////////////////////////////////////////////////
#ifdef _VECTOR_
template <typename T>
ByteBuffer& operator<<(ByteBuffer& b, const std::vector<T>& v)
{
b << (uint32)v.size();
typename std::vector<T>::const_iterator iter = v.begin();
typename std::vector<T>::const_iterator& iEnd = v.end();
for (; iter != iEnd; ++iter)
{
b << *iter;
}
return b;
}
template <typename T>
ByteBuffer& operator>>(ByteBuffer& b, std::vector<T>& v)
{
uint32 vsize;
b >> vsize;
v.clear();
while (vsize--)
{
T t;
b >> t;
v.push_back(t);
}
return b;
}
#endif
//////////////////////////////////////////////////////////////////////////
// std::list
//////////////////////////////////////////////////////////////////////////
#ifdef _LIST_
template <typename T>
ByteBuffer& operator<<(ByteBuffer& b, const std::list<T>& v)
{
b << (uint32)v.size();
typename std::list<T>::const_iterator iter = v.begin();
typename std::list<T>::const_iterator& iEnd = v.end();
for (; iter != iEnd; ++iter)
{
b << *iter;
}
return b;
}
template <typename T>
ByteBuffer& operator>>(ByteBuffer& b, std::list<T>& v)
{
uint32 vsize;
b >> vsize;
v.clear();
while (vsize--)
{
T t;
b >> t;
v.push_back(t);
}
return b;
}
#endif
//////////////////////////////////////////////////////////////////////////
// std::map
//////////////////////////////////////////////////////////////////////////
#ifdef _MAP_
template <typename K, typename V>
ByteBuffer& operator<<(ByteBuffer& b, const std::map<K, V>& m)
{
b << (uint32)m.size();
typename std::map<K, V>::const_iterator iter = m.begin();
typename std::map<K, V>::const_iterator iEnd = m.end();
for (; iter != iEnd; ++iter)
{
b << iter->first << iter->second;
}
return b;
}
template <typename K, typename V>
ByteBuffer &operator>>(ByteBuffer& b, std::map<K, V>& m)
{
uint32 msize;
b >> msize;
m.clear();
while (msize--)
{
K k;
V v;
b >> k >> v;
m.insert(std::make_pair(k, v));
}
return b;
}
#endif
如何利用ByteBuffer序列化和反序列化
假设我们要序列化std::string的数据,那么我们这样做:
std::string str;
ByteBuffer buf;
buf << str;那么,如何将这个str反序列化出来呢?这样做:
std::string str;
ByteBuffer buf;
buf >> str;
So Easy!是吧。具体在TCP收发包的实际场景中怎样做,我也不多废话,请看下面下载提供的代码便是了。
在实用下细节上的一些区别
通常情况下,一个协议的数据集会定义为一个struct,然后重载其<<和>>算符用于序列化和反序列化。这个如果仅仅是在C++下倒还好,但如若放置在混合语言编程的情况下,这可能就不行了,很多语言是不支持算符重载的。如若纯逻辑都在lua或者python神马里面做,我们只能为每个基本类型写一个read和write的方法:readInt8,readInt16,,readString,writeInt8,writeInt16,writeString等等。然后在每个协议处理方法里面按照顺序逐个的处理协议数据集的数据,这样是很容易出问题的,却也是没有办法的办法了。
Google Protocol Buffer(ProtoBuf)
在开源工具里面,不得不提到的就是它了,它很适合于混合语言的情况下使用。它自己有一套自己的数据描述语言,数据序列化的描述都写在.proto。只需要写一次.proto文件,便可以在多语言里面使用了该协议了。比如,我曾经做过一个VC+Flash AS3的项目,就是用的它。如果没有它,网络协议我必须在c++里面定义一次,flash里面再定义一次,那可真真是麻烦死了,麻烦倒还是小事情,如果两边的定义不同步的话,序列化或者反序列化就会发生错误,那可就糟糕了。
如果有多语言的需求,最好就是使用像ProtoBuf这样的解决方案。当然,如果没有跨语言的需求,还是尽量简单为好,比如上面的ByteBuffer,毕竟简单的东西自己可以比较轻松的掌控。
主页地址:http://code.google.com/p/protobuf/
代码下载:testByteBuffer.rar
EDIT:
time_t解序列化写错了,参数应该是一个传出值,为一个引用,但是我把引用符给忘记了。特此订正!
ByteBuffer& operator>>(time_t& value)
{
value = read<time_t>();
return *this;
}
====================
http://www.cnblogs.com/jiangtong/archive/2012/03/22/2411985.html
1. TCP提供了面向“连续字节流”的可靠的传输服务,TCP并不理解流所携带的数据内容,这个内容需要应用层自己解析。
2. “字节流”是连续的、非结构化的,而我们的应用需要的是有序的、结构化的数据信息,因此我们需要定义自己的“规则”去解读这个“连续的字节流“,那解决途径就是定义自己的封包类型,然后用这个类型去映射“连续字节流”。
如何定义封包,我们回顾一下前面这个数据进入协议栈的封装过程图:

封包其实就是将上图中进入协议栈的用户数据[即用户要发送的数据]定义为一种方便识别和交流的类型,这有点类似信封的概念,信封就是一种人们之间通信的格式,信封格式如下:
信封格式:
收信人邮编
收信人地址
收信人姓名
信件内容
那么在程序里面我们也需要定义这种格式:在C++里面只有结构和类这种两种类型适合表达这个概念了。网络上很多朋友对此表述了自己的看法并贴出了代码:比如
/************************************************************************/
/* 数据封包信息定义开始 */
/************************************************************************/
#pragma pack(push,1) //将原对齐方式压栈,采用新的1字节对齐方式
/* 封包类型枚举[此处根据需求列举] */
typedef enum{
NLOGIN=1,
NREG=2,
NBACKUP=3,
NRESTORE=3,
NFILE_TRANSFER=4,
NHELLO=5
} PACKETTYPE;
/* 包头 */
typedef struct tagNetPacketHead{
byte version;//版本
PACKETTYPE ePType;//包类型
WORD nLen;//包体长度
} NetPacketHead;
/* 封包对象[包头&包体] */
typedef struct tagNetPacket{
NetPacketHead netPacketHead;//包头
char * packetBody;//包体
} NetPacket;
#pragma pack(pop)
/**************数据封包信息定义结束**************************/
3. 发包顺序与收包问题
a) 由于TCP要通过协商解决发送出去的报文段的长度,因此我们发送的数据很有可能被分割甚至被分割后再重组交给网络层发送,而网络层又是采用分组传送,即网络层数据报到达目标的顺序完全无法预测,那么收包会出现半包、粘包问题。举个例子,发送端连续发送两端数据msg1和msg2,那么发送端[传输层]可能会出现以下情况:
i. Msg1和msg2小于TCP的MSS,两个包按照先后顺序被发出,没有被分割和重组
ii. Msg1过大被分割成两段TCP报文msg1-1、msg1-2进行传送,msg2较小直接被封装成一个报文传送
iii. Msg1过大被分割成两段TCP报文msg1-1、msg1-2,msg1-1先被传送,剩下的msg1-2和msg2[较小]被组合成一个报文传送
iv. Msg1过大被分割成两段TCP报文msg1-1、msg1-2,msg1-1先被传送,剩下的msg1-2和msg2[较小]组合起来还是太小,组合的内容在和后面再发送的msg3的前部分数据组合起来发送
v. ……………………….太多……………………..
b) 接收端[传输层]可能出现的情况
i. 先收到msg1,再收到msg2,这种方式太顺利了。
ii. 先收到msg1-1,再收到msg1-2,再收到msg2
iii. 先收到msg1,再收到msg2-1,再收到msg2-2
iv. 先收到msg1和msg2-1,再收到msg2-2
v. //…………还有很多………………
c) 其实“接收端网络层”接收到的分组数据报顺序和发送端比较可能完全是乱的,比如发“送端网络层”发送1、2、3、4、5,而接收端网络层接收到的数据报顺序却可能是2、1、5、4、3,但是“接收端的传输层”会保证链接的有序性和可靠性,“接收端的传输层”会对“接收端网络层”收到的顺序紊乱的数据报重组成有序的报文[即发送方传输层发出的顺序],然后交给“接收端应用层”使用,所以“接收端传输层”总是能够保证数据包的有序性,“接收端应用层”[我们编写的socket程序]不用担心接收到的数据的顺序问题。
d) 但是如上所述,粘包问题和半包问题不可避免。我们在接收端应用层需要自己编码处理粘包和半包问题。一般做法是定义一个缓冲区或者是使用标准库/框架提供的容器循环存放接收到数据,边接收变判断缓冲区数据是否满足包头大小,如果满足包头大小再判断缓冲区剩下数据是否满足包体大小,如果满足则提取。详细步骤如下:
1. 接收数据存入缓冲区尾部
2. 缓冲区数据满足包头大小否
3. 缓冲区数据不满足包头大小,回到第1步;缓冲区数据满足包头大小则取出包头,接着判断缓冲区剩余数据满足包头中定义的包体大小否,不满足则回到第1步。
4. 缓冲区数据满足一个包头大小和一个包体大小之和,则取出包头和包体进行使用,此处使用可以采用拷贝方式转移缓冲区数据到另外一个地方,也可以为了节省内存直接采取调用回调函数的方式完成数据使用。
5. 清除缓冲区的第一个包头和包体信息,做法一般是将缓冲区剩下的数据拷贝到缓冲区首部覆盖“第一个包头和包体信息”部分即可。(不太好,分别维护读写指针)
可参考http://blog.csdn.net/solstice/article/details/6329080
粘包、半包处理具体实现很多朋友都有自己的做法,比如最前面贴出的链接,这里我也贴出一段参考:
缓冲区实现头文件:
#include <windows.h>
#ifndef _CNetDataBuffer_H_
#define _CNetDataBuffer_H_
#ifndef TCPLAB_DECLSPEC
#define TCPLAB_DECLSPEC _declspec(dllimport)
#endif
/************************************************************************/
/* 数据封包信息定义开始 */
/************************************************************************/
#pragma pack(push,1) //将原对齐方式压栈,采用新的1字节对齐方式
/* 封包类型枚举[此处根据协议需求列举] */
typedef enum{
NLOGIN=1,
NREG=2,
NBACKUP=3,
NRESTORE=3,
NFILE_TRANSFER=4,
NHELLO=5
} PACKETTYPE;
/* 包头 */
typedef struct tagNetPacketHead{
byte version;//版本
PACKETTYPE ePType;//包类型
WORD nLen;//包体长度
} NetPacketHead;
/* 封包对象[包头&包体] */
typedef struct tagNetPacket{
NetPacketHead netPacketHead;//包头
char * packetBody;//包体
} NetPacket;
#pragma pack(pop)
/**************数据封包信息定义结束**************************/
//缓冲区初始大小
#define BUFFER_INIT_SIZE 2048
//缓冲区膨胀系数[缓冲区膨胀后的大小=原大小+系数*新增数据长度]
#define BUFFER_EXPAND_SIZE 2
//计算缓冲区除第一个包头外剩下的数据的长度的宏[缓冲区数据总长度-包头大小]
#define BUFFER_BODY_LEN (m_nOffset-sizeof(NetPacketHead))
//计算缓冲区数据当前是否满足一个完整包数据量[包头&包体]
#define HAS_FULL_PACKET ( \
(sizeof(NetPacketHead)<=m_nOffset) && \
((((NetPacketHead*)m_pMsgBuffer)->nLen) <= BUFFER_BODY_LEN) \
)
//检查包是否合法[包体长度大于零且包体不等于空]
#define IS_VALID_PACKET(netPacket) \
((netPacket.netPacketHead.nLen>0) && (netPacket.packetBody!=NULL))
//缓冲区第一个包的长度(包头和包体的长度和)
#define FIRST_PACKET_LEN (sizeof(NetPacketHead)+((NetPacketHead*)m_pMsgBuffer)->nLen)
/* 数据缓冲 */
class /*TCPLAB_DECLSPEC*/ CNetDataBuffer
{
/* 缓冲区操作相关成员 */
private:
char *m_pMsgBuffer;//数据缓冲区
int m_nBufferSize;//缓冲区总大小
int m_nOffset;//缓冲区数据大小
public:
int GetBufferSize() const;//获得缓冲区中现有数据的大小
BOOL ReBufferSize(int);//调整缓冲区的大小
BOOL IsFitPacketHeadSize() const;//缓冲区中的现有的数据大小是否足够一个包头大小
BOOL IsHasFullPacket() const;//缓冲区是否至少拥有一个完整的包数据[包含包头和包体]
BOOL AddMsg(char *pBuf,int nLen);//添加消息到缓冲区
const char *GetBufferContents() const;//得到缓冲区内容
void Reset();//缓冲区复位[清空缓冲区数据,但并未释放缓冲区]
void Poll();//移除缓冲区首部的第一个数据包
public:
CNetDataBuffer();
~CNetDataBuffer();
};
#endif
缓冲区实现文件:
#define TCPLAB_DECLSPEC _declspec(dllexport)
#include "CNetDataBuffer.h"
/* 构造 */
CNetDataBuffer::CNetDataBuffer()
{
m_nBufferSize = BUFFER_INIT_SIZE;//设置缓冲区大小
m_nOffset = 0;//设置数据偏移值[数据大小]为0
m_pMsgBuffer = NULL;
m_pMsgBuffer = new char[BUFFER_INIT_SIZE];//分配缓冲区为初始大小
ZeroMemory(m_pMsgBuffer,BUFFER_INIT_SIZE);//缓冲区清空
}
/* 析构 */
CNetDataBuffer::~CNetDataBuffer()
{
if (m_nOffset!=0)
{
delete [] m_pMsgBuffer;//释放缓冲区
m_pMsgBuffer = NULL;
m_nBufferSize=0;
m_nOffset=0;
}
}
/************************************************************************/
/* Description: 获得缓冲区中数据的大小 */
/* Return: 缓冲区中数据的大小 */
/************************************************************************/
INT CNetDataBuffer::GetBufferSize() const
{
return this->m_nOffset;
}
/************************************************************************/
/* Description: 缓冲区中的数据大小是否足够一个包头大小 */
/* Return: 如果满足则返回True,否则返回False
/************************************************************************/
BOOL CNetDataBuffer::IsFitPacketHeadSize() const
{
return sizeof(NetPacketHead)<=m_nOffset;
}
/************************************************************************/
/* Description: 判断缓冲区是否拥有完整的数据包(包头和包体) */
/* Return: 如果缓冲区包含一个完整封包则返回True,否则False */
/************************************************************************/
BOOL CNetDataBuffer::IsHasFullPacket() const
{
//如果连包头大小都不满足则返回
//if (!IsFitPacketHeadSize())
// return FALSE;
return HAS_FULL_PACKET;//此处采用宏简化代码
}
/************************************************************************/
/* Description: 重置缓冲区大小 */
/* nLen: 新增加的数据长度 */
/* Return: 调整结果 */
/************************************************************************/
BOOL CNetDataBuffer::ReBufferSize(int nLen)
{
char *oBuffer = m_pMsgBuffer;//保存原缓冲区地址
try
{
nLen=(nLen<64?64:nLen);//保证最小增量大小
//新缓冲区的大小=增加的大小+原缓冲区大小
m_nBufferSize = BUFFER_EXPAND_SIZE*nLen+m_nBufferSize;
m_pMsgBuffer = new char[m_nBufferSize];//分配新的缓冲区,m_pMsgBuff指向新缓冲区地址
ZeroMemory(m_pMsgBuffer,m_nBufferSize);//新缓冲区清零
CopyMemory(m_pMsgBuffer,oBuffer,m_nOffset);//将原缓冲区的内容全部拷贝到新缓冲区
}
catch(...)
{
throw;
}
delete []oBuffer;//释放原缓冲区
return TRUE;
}
/************************************************************************/
/* Description: 向缓冲区添加消息 */
/* pBuf: 要添加的数据 */
/* nLen: 添加的消息长度
/* return: 添加成功返回True,否则False */
/************************************************************************/
BOOL CNetDataBuffer::AddMsg(char *pBuf,int nLen)
{
try
{
//检查缓冲区长度是否满足,不满足则重新调整缓冲区大小
if (m_nOffset+nLen>m_nBufferSize)
ReBufferSize(nLen);
//拷贝新数据到缓冲区末尾
CopyMemory(m_pMsgBuffer+sizeof(char)*m_nOffset,pBuf,nLen);
m_nOffset+=nLen;//修改数据偏移
}
catch(...)
{
return FALSE;
}
return TRUE;
}
/* 得到缓冲区内容 */
const char * CNetDataBuffer::GetBufferContents() const
{
return m_pMsgBuffer;
}
/************************************************************************/
/* 缓冲区复位 */
/************************************************************************/
void CNetDataBuffer::Reset()
{
if (m_nOffset>0)
{
m_nOffset = 0;
ZeroMemory(m_pMsgBuffer,m_nBufferSize);
}
}
/************************************************************************/
/* 移除缓冲区首部的第一个数据包 */
清除缓冲区的第一个包头和包体信息,这里的做法是将缓冲区剩下的数据拷贝到缓冲区首部覆盖“第一个包头和包体信息”部分即可。(不太好,分别维护读写指针)
可参考http://blog.csdn.net/solstice/article/details/6329080
/************************************************************************/
void CNetDataBuffer::Poll()
{
if(m_nOffset==0 || m_pMsgBuffer==NULL)
return;
if (IsFitPacketHeadSize() && HAS_FULL_PACKET)
{
CopyMemory(m_pMsgBuffer,m_pMsgBuffer+FIRST_PACKET_LEN*sizeof(char),m_nOffset-FIRST_PACKET_LEN);
}
}
对TCP发包和收包进行简单封装:
头文件:
#include <windows.h>
#include "CNetDataBuffer.h"
// #ifndef TCPLAB_DECLSPEC
// #define TCPLAB_DECLSPEC _declspec(dllimport)
// #endif
#ifndef _CNETCOMTEMPLATE_H_
#define _CNETCOMTEMPLATE_H_
//通信端口
#define TCP_PORT 6000
/* 通信终端[包含一个Socket和一个缓冲对象] */
typedef struct {
SOCKET m_socket;//通信套接字
CNetDataBuffer m_netDataBuffer;//该套接字关联的数据缓冲区
} ComEndPoint;
/* 收包回调函数参数 */
typedef struct{
NetPacket *pPacket; //对应具体的协议类型
LPVOID processor;
SOCKET comSocket;
} PacketHandlerParam;
class CNetComTemplate{
/* Socket操作相关成员 */
private:
public:
void SendPacket(SOCKET m_connectedSocket,NetPacket &netPacket);//发包函数
BOOL RecvPacket(ComEndPoint &comEndPoint,void (*recvPacketHandler)(LPVOID)=NULL,LPVOID=NULL);//收包函数
public:
CNetComTemplate();
~CNetComTemplate();
};
#endif
实现文件:
#include "CNetComTemplate.h"
CNetComTemplate::CNetComTemplate()
{
}
CNetComTemplate::~CNetComTemplate()
{
}
/************************************************************************/
/* Description:发包 */
/* m_connectedSocket:建立好连接的套接字 */
/* netPacket:要发送的数据包 */
/************************************************************************/
void CNetComTemplate::SendPacket(SOCKET m_connectedSocket,NetPacket &netPacket)
{
if (m_connectedSocket==NULL || !IS_VALID_PACKET(netPacket))//如果尚未建立连接则退出
{
return;
}
::send(m_connectedSocket,(char*)&netPacket.netPacketHead,sizeof(NetPacketHead),0);//先发送包头
::send(m_connectedSocket,netPacket.packetBody,netPacket.netPacketHead.nLen,0);//再发送包体
}
/**************************************************************************/
/* Description:收包 */
/* comEndPoint:通信终端[包含套接字和关联的缓冲区] */
/* recvPacketHandler:收包回调函数,当收到一个包后调用该函数进行包的分发处理*/
/**************************************************************************/
BOOL CNetComTemplate::RecvPacket(ComEndPoint &comEndPoint,void (*recvPacketHandler)(LPVOID),LPVOID pCallParam)
{
if (comEndPoint.m_socket==NULL)
return FALSE;
int nRecvedLen = 0;
char pBuf[1024];
//如果缓冲区数据不够包大小则继续从套接字读取tcp报文段[这里是阻塞的方式读,性能不行]
while (!(comEndPoint.m_netDataBuffer.IsHasFullPacket()))
{
nRecvedLen = recv(comEndPoint.m_socket,pBuf,1024,0);
if (nRecvedLen==SOCKET_ERROR || nRecvedLen==0)//若果Socket错误或者对方连接已经正常关闭则结束读取
break;
comEndPoint.m_netDataBuffer.AddMsg(pBuf,nRecvedLen);//将新接收的数据存入缓冲区
}
//执行到此处可能是三种情况:
//1.已经读取到的数据满足一个完整的tcp报文段
//2.读取发生socket_error错误
//3.在还未正常读取完毕的过程中对方连接已经关闭
//如果没有读取到数据或者没有读取到完整报文段则返回返回
if (nRecvedLen==0 || (!(comEndPoint.m_netDataBuffer.IsHasFullPacket())))
{
return FALSE;
}
if (recvPacketHandler!=NULL)
{
//构造准备传递给回调函数的数据包
NetPacket netPacket;
netPacket.netPacketHead = *(NetPacketHead*)comEndPoint.m_netDataBuffer.GetBufferContents();
netPacket.packetBody = new char[netPacket.netPacketHead.nLen];//动态分配包体空间
//构造回调函数参数,这里没有序列化相关的操作,纯手工从裸数据构造出结构体
PacketHandlerParam packetParam;
packetParam.pPacket = &netPacket;
packetParam.processor = pCallParam;
//呼叫回调函数
recvPacketHandler(&packetParam);
delete []netPacket.packetBody;
}
//移除缓冲区的第一个包
comEndPoint.m_netDataBuffer.Poll();
return TRUE;
}
浙公网安备 33010602011771号