php 反序列化后面有省略号,分享套接字数据包序列化与反序列化方法

本文介绍了如何对TCP/UDP通信中的数据包进行序列化和反序列化,通过定义数据包格式,包括包头和包体,详细阐述了数据对象的结构,并提供了一个C#实现的示例,利用特性简化了类的标记,方便进行序列化和反序列化操作。此外,还提及了如何处理数据通信中可能出现的问题。
摘要由CSDN通过智能技术生成

分享套接字数据包序列化与反序列化方法“

简单说一下,本文不涉及Socket的连接、数据接收,只是对数据包(byte[])的序列化和反序列化方法的封装介绍。

本文目录本文背景

一般操作

本文操作

总结

1.本文背景

经常做C/S,客户端与服务端通信基本是TCP/UDP通信,套接字用得飞起。

比如我们有一个系统,这个系统又分几个系统子模块进程:C++服务端

Android 客户端

iOS 客户端

WPF桌面管理端 ......

几个模块之间通过TCP或者UDP通信,数据包解析与组装是常规操作,我们定义数据包格式如下:

一个数据包包含包头和包体,定义如下:

包头

序号字段名数据类型备注1消息标识int用于标识数据包是否合法

2名称string当前消息名称,用于标识数据包类型

3版本号int当前消息版本号,允许程序中消息存在多个版本,用于版本迭代

包含这三个字段:消息标识、名称、版本号,唯一确定消息对象。

包体

序号字段名数据类型备注1字段1数据类型字段1

2字段2数据类型字段2

包体直接定义字段信息,就像定义类属性一样。

另包头与包体中数据类型定义如下:

数据包字段类型定义

序号数据类型备注1int4个字节的整型值

2string组成格式:字符串实际值字节长度(2个字节)+字符串实际值byte

3char单字节值

4列表组成格式:4个字节列表长度+列表实际数据值byte

5字典同上,具体看源码

其他数据类型类似,复杂数据类型使用4个字节的值字节长度+实际值byte。

给一个测试数据包

序号字段名数据类型备注1消息标识int取值:0x4A534604

2消息名称string三国信息,取值:"ThreeCountries"

3版本号int取值:1

4编号int给三国一个编号吧,取值:1

5国名string取值:"蜀国"

6皇帝string取值:"刘备"

7大将个数int5

8大将1编号int取值:1

9大将1名字string取值:"张飞"

10大将1备注string取值:"三板斧"

11大将2编号int取值:2

12大将2名字string取值:"关羽"

13大将2备注string取值:"青龙偃月刀"

14大将3编号int取值:3

15大将3名字string取值:"赵云"

16大将3备注string取值:"很猛的"

17大将4编号int取值:4

18大将4名字string取值:"马超"

19大将4备注string取值:"强"

20大将5编号int取值:5

21大将5名字string取值:"黄忠"

22大将5备注string取值:"老当益壮"

大致理解下:前三个字段是包体:用于标识整个数据包,便于包体解析;

后面的包体,简单说就是三国中的国家信息简介,前三个字段为三国中的一个国家基本信息:编号、国名、皇帝,后面是该国家大将信息列表,每个大将有编号、名称、备注等。

定义数据对象

根据数据包定义,我们可以很快定义类进行使用,不管你是C++还是Java。下面是我用C#写的对应类,用于序列化与反序列化使用:/// 

/// 三国

/// 

public class ThreeCountries

{

/// 

/// 获取或者设置 ID

/// 

public int ID { get; set; }

/// 

/// 获取或者设置 国名

/// 

public string Name { get; set; }

/// 

/// 获取或者设置 皇帝

/// 

public string Emperor { get; set; }

/// 

/// 获取或者设置 所选课程列表

/// 

public List Courses { get; set; }

public override string ToString()

{

return $"三国之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大将";

}

}

/// 

/// 三国名将

/// 

public class FamousGeneral

{

/// 

/// 获取或者设置 编号

/// 

public int ID { get; set; }

/// 

/// 获取或者设置 名字

/// 

public string Name { get; set; }

/// 

/// 获取或者设置 描述

/// 

public string Memo { get; set; }

public override string ToString()

{

return $"{ID}:{Name}=>{Memo}";

}

}

对于上面给的数据包你怎么序列化及反序列化?转换成数据如下,下节接着讨论ThreeCountries shuKingdom = new ThreeCountries

{

ID = 1,

Name = "蜀国",

Emperor = "刘备",

Courses = new System.Collections.Generic.List

{

new FamousGeneral{ ID=1,Name="张飞",Memo="三板斧"},

new FamousGeneral{ ID=2,Name="关羽",Memo="青龙偃月刀"},

new FamousGeneral{ ID=3,Name="赵云",Memo="很猛的"},

new FamousGeneral{ ID=3,Name="马超",Memo="强"},

new FamousGeneral{ ID=3,Name="黄忠",Memo="老当益壮"},

}

};

2. 常规操作

序列化

代码太繁琐,我就写个不正规的伪代码吧定义一个byte数组;

一、写包头

1、写入4字节的消息标识:0x4A534604

计算消息对象名称字符串“ThreeCountries”长度,及转换字符串为byte数组

2、写入2字节的bytes数组长度,写入实际的byte数组值

3、写入4字节的消息版本号

二、写包体

4、写入4字节的大将个数

循环每个大将信息,依次写入

5、写入大将1编号

6、写入大将1名称

7、写入大奖1备注

8、写入大将2编号

9、写入大将3名称

10、写入大奖4备注

...写吐了,省略号

反序列化不想写了,累

常规操作

定义一个序列化接口,每个网络对象实现其中的序列化与反序列化接口public interface ISerializeInterface

{

byte[] Serialize(T t);

T Deserialize(byte[] arr);

}

public class ThreeCountries : ISerializeInterface

{

public byte[] Serialize(T t)

{

// 将上面的序列化代码写在这

}

T Deserialize(byte[] arr)

{

// 将上面的反序列化代码写在这,不好意思我没写

}

}

3. 本文操作

写了半天的Demo,文章可能就写的有点水了,我估计读者也不会仔细看代码,直接去Github check项目去了,哈哈。

我还是简单说说吧,实现很简单,定义一些特性,下面红框里的代码文件:

4ceaf8cd33211cfa38667e768ee2304f.png序列化特性及帮助类

使用很简单,在上面的数据类上加上特性,改动不多,看下面代码:/// 

/// 三国

/// 

[NetObject(Name = "ThreeCountries", Version = 1)]

public class ThreeCountries

{

/// 

/// 获取或者设置 ID

/// 

[NetObjectProperty(ID = 1)]

public int ID { get; set; }

/// 

/// 获取或者设置 国名

/// 

[NetObjectProperty(ID = 2)]

public string Name { get; set; }

/// 

/// 获取或者设置 皇帝

/// 

[NetObjectProperty(ID = 3)]

public string Emperor { get; set; }

/// 

/// 获取或者设置 所选课程列表

/// 

[NetObjectProperty(ID = 4)]

public List Courses { get; set; }

public static NetObjectAttribute CurrentObject = null;

static ThreeCountries()

{

CurrentObject = NetObjectSerializeHelper.GetAttribute(default(ThreeCountries));

}

public override string ToString()

{

return $"三国之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大将";

}

}

/// 

/// 三国名将

/// 

public class FamousGeneral

{

/// 

/// 获取或者设置 编号

/// 

[NetObjectProperty(ID = 1)]

public int ID { get; set; }

/// 

/// 获取或者设置 名字

/// 

[NetObjectProperty(ID = 2)]

public string Name { get; set; }

/// 

/// 获取或者设置 描述

/// 

[NetObjectProperty(ID = 3)]

public string Memo { get; set; }

public override string ToString()

{

return $"{ID}:{Name}=>{Memo}";

}

}

仔细看的话,只在外层类(ThreeCountries)上加了NetObject特性,和属性上加了NetObjectProperty特性,分别标识消息名称、版本号及每个属性的序列化与反序列化顺序即可,类中使用的子对象Courses属性,也只需要加属性特性即可,如上。

下面添加单元测试,并且测试通过:

966e9814800805f8b7f4dad97f0249c8.png单元测试通过

4. 总结

用这套代码(demo,有所改变,但也差不多),完成了几个类似的项目,每次数据通信联调、测试问题,C++和java的同事找我时,我就说:

"你先看你自己数据包的序列化和反序列化代码有没有问题,我这不会出问题的,完全按数据包格式转的。"

刚开始还在那闹,后面定位几次问题后,类似的问题他们就没再找我了,偷笑中。

demo写了半天,还是有点累,源码:见开源项目TerminalMACS。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值