最简单的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;
 }
目录
相关文章
|
6月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
324 1
|
6月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
326 1
|
9月前
|
弹性计算 运维 Kubernetes
看阿里云操作系统控制台如何一招擒拿网络丢包
阿里云操作系统控制台帮忙客户快速定位问题,不仅成功完成业务部署并实现稳定运行,更有效遏制了持续性成本消耗。
|
网络协议 物联网
VB6网络通信软件上位机开发,TCP网络通信,读写数据并处理,完整源码下载
本文介绍使用VB6开发网络通信上位机客户端程序,涵盖Winsock控件的引入与使用,包括连接服务端、发送数据(如通过`Winsock1.SendData`方法)及接收数据(利用`Winsock1_DataArrival`事件)。代码实现TCP网络通信,可读写并处理16进制数据,适用于自动化和工业控制领域。提供完整源码下载,适合学习VB6网络程序开发。 下载链接:[完整源码](http://xzios.cn:86/WJGL/DownLoadDetial?Id=20)
483 12
|
Kubernetes Shell Windows
【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享
在AKS中遇到复杂网络问题时,可通过以下步骤进入特定POD抓取网络包进行分析:1. 使用`kubectl get pods`确认Pod所在Node;2. 通过`kubectl node-shell`登录Node;3. 使用`crictl ps`找到Pod的Container ID;4. 获取PID并使用`nsenter`进入Pod的网络空间;5. 在`/var/tmp`目录下使用`tcpdump`抓包。完成后按Ctrl+C停止抓包。
480 12
|
负载均衡 网络协议 算法
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
这网络层就像搭积木一样,上层协议都是基于下层协议搭出来的。不管是ping(用了ICMP协议)还是tcp本质上都是基于网络层IP协议的数据包,而到了物理层,都是二进制01串,都走网卡发出去了。 如果网络环境没发生变化,目的地又一样,那按道理说他们走的网络路径应该是一样的,什么情况下会不同呢? 我们就从路由这个话题聊起吧。
550 4
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
|
JSON 算法 Java
Nettyの网络聊天室&扩展序列化算法
通过本文的介绍,我们详细讲解了如何使用Netty构建一个简单的网络聊天室,并扩展序列化算法以提高数据传输效率。Netty的高性能和灵活性使其成为实现各种网络应用的理想选择。希望本文能帮助您更好地理解和使用Netty进行网络编程。
245 12
|
前端开发 网络协议 安全
【网络原理】——HTTP协议、fiddler抓包
HTTP超文本传输,HTML,fiddler抓包,URL,urlencode,HTTP首行方法,GET方法,POST方法
|
网络协议 测试技术 Linux
Golang 实现轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库
gev 是一个基于 epoll 和 kqueue 实现的高性能事件循环库,适用于 Linux 和 macOS(Windows 暂不支持)。它支持多核多线程、动态扩容的 Ring Buffer 读写缓冲区、异步读写和 SO_REUSEPORT 端口重用。gev 使用少量 goroutine,监听连接并处理读写事件。性能测试显示其在不同配置下表现优异。安装命令:`go get -u github.com/Allenxuxu/gev`。
334 0
|
网络协议
TCP报文格式全解析:网络小白变高手的必读指南
本文深入解析TCP报文格式,涵盖源端口、目的端口、序号、确认序号、首部长度、标志字段、窗口大小、检验和、紧急指针及选项字段。每个字段的作用和意义详尽说明,帮助理解TCP协议如何确保可靠的数据传输,是互联网通信的基石。通过学习这些内容,读者可以更好地掌握TCP的工作原理及其在网络中的应用。

热门文章

最新文章

下一篇
开通oss服务