牵着老婆满街逛

严以律己,宽以待人. 三思而后行.
GMail/GTalk: yanglinbo#google.com;
MSN/Email: tx7do#yahoo.com.cn;
QQ: 3 0 3 3 9 6 9 2 0 .

最简单的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;
}

posted on 2011-05-07 01:33 杨粼波 阅读(12899) 评论(12)  编辑 收藏 引用 所属分类: 原创文章网络编程C++

评论

# re: 最简单的TCP网络封包解包(补充)-序列化[未登录] 2011-05-07 09:56 true

mongos里面的ByteBuffer确实好用,以前也做过把他单独提取出来,再添加几个方法使用  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-07 10:27 杨粼波

我是从arcemu里面拿出来的,感觉好像和mongos里面的是一样的。

还有一种利用std::stringstream做序列化的,倒也还可以。

用std::vector可以利用STL的内存分配器,这个比较省心。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-07 12:08 饭中淹

我使用的是类型加数值的序列化和反序列化,封包不仅用于网络,还用于db,内部消息等地方。
  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-07 12:10 饭中淹

这样做有个好处是不需要定型的struct,处理过程只要传入一个复合类型value的数组即可,对于统一整体架构,减少代码工作量很有帮助。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化[未登录] 2011-05-07 12:12 杨粼波

@饭中淹
还有一个,就是数据持久化。

嗯。。。。反正是IO神马的地方都可以用用。
我的服务器事件系统的一些数据有的也是用这个传递。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化[未登录] 2011-05-07 12:15 杨粼波

@饭中淹

我没有尝试过“类型加数值的序列化和反序列化”,这个的空间不太好吧?会相对多吃点字节数吧?  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-07 12:37 饭中淹

@杨粼波
目前的方式是用单字节来表示一个类型。
类型有 int, uint, float, string

int, uint, float 又有数组类型。

string和array是带16位的Length字段。

array除了1字节的array类型指定,还带一个1字节的元素数据类型。


对整体容量的增加,有限。

不过,现在这种方式并不是最好的方式。

我认为的最好的方式,所有类型都提取成一个数据类型对象,也就是类似GOOGLE PROTOCOL BUFFER的用额外的描述生成的一个对结构体的描述。这个方式是跟我的数据对象和映射的整体逻辑架构相关的。目标是实现服务器端,在对所有数据和逻辑的描述上形成的整体架构的统一,同时将类型和表义信息从最终数据存储中去掉。

不过这种情况下,可能会出现版本问题,由于双方描述的版本差异,导致兼容性问题。我的解决方法,是把原有的类型信息,更换为FIELDINDEX信息,也就是字段索引。在结构体描述的更改过程中,遵循字段出现增加,就增长其FIELDINDEX的原则。这样,在有限的版本空间内,FIELDINDEX会精确对应到相应的字段上。如果FIELDINDEX超出值域限制,那只有放到新的结构描述中去了。

我在网络封包这块,有着很长的一个摸索过程,大概经历过以下几个阶段:
1- 结构体直接作为封包发送
2- 结构体序列化(封包内只有数据本身,手动编写序列化和反序列化的方法)
3- 结构体序列化(封包内带有类型信息,手动编写序列化和反序列化的方法)
4- 数据对象和数据映射(封包内带有字段信息,通过数据映射来序列化和反序列化)




  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化[未登录] 2011-05-07 13:04 杨粼波

@饭中淹

和我想的差不多。
比XML要紧凑一些。
以前看Jabber的时候,它是用XML序列化,不过,空间代价太大了。

版本兼容性,这是一个问题。我现在的做法是完全不兼容。只要是协议版本不一致,就强制性升级更新。我觉得这是一个简单有效的方法。如果还要兼容协议版本,做的事情多得多,这样就得耗费大量的时间在上面,而且对稳定性有一定的影响。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-09 10:22 zuhd

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;
}
======================================
小伙子,怎么支持vector<vector<int> >的数据类型啊?  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2011-05-14 11:21 杨粼波

@zuhd
C++的模板是支持递归的,是没问题的。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化 2015-06-05 09:20 yezhibin000

有点小疑问:如果发送的字符串中如果有'\0',比如"wo是alk\0\0sl3alsfkl123";这样的字符串,,那么后面的"\0\0sl3alsfkl123"通通没有收到。  回复  更多评论   

# re: 最简单的TCP网络封包解包(补充)-序列化[未登录] 2015-06-12 15:27 杨粼波

@yezhibin000

字符串是以\0作为结尾的,就字符串而言,后面的字符肯定是被丢弃掉了。
至于你有没有发送成功,接收成功,这又要分开来看,分开来测试。你有没有正确的获取长度以发送成功,你有没有正确的长度去接收数据呢?
另,你这样做有什么意义呢?  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理