分享套接字数据包序列化与反序列化方法
分享套接字数据包序列化与反序列化方法
简单说一下,本文不涉及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 |
包体直接定义字段信息,就像定义类属性一样。
另包头与包体中数据类型定义如下:
数据包字段类型定义
序号 | 数据类型 | 备注 |
---|---|---|
1 | int | 4个字节的整型值 |
2 | string | 组成格式:字符串实际值字节长度(2个字节)+字符串实际值byte |
3 | char | 单字节值 |
4 | 列表 | 组成格式:4个字节列表长度+列表实际数据值byte |
5 | 字典 | 同上,具体后源码 |
其他数据类型类似,复杂数据类型使用4个字节的值字节长度+实际值byte。
给一个测试数据包
序号 | 字段名 | 数据类型 | 备注 |
---|---|---|---|
1 | 消息标识 | int | 取值:0x4A534604 |
2 | 消息名称 | string | 三国信息,取值:"ThreeCountries" |
3 | 版本号 | int | 取值:1 |
4 | 编号 | int | 给三国一个编号吧,取值:1 |
5 | 国名 | string | 取值:"蜀国" |
6 | 皇帝 | string | 取值:"刘备" |
7 | 大将个数 | int | 5 |
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#写的对应类,用于序列化与反序列化使用:
/// <summary> /// 三国 /// </summary> public class ThreeCountries { /// <summary> /// 获取或者设置 ID /// </summary> public int ID { get; set; } /// <summary> /// 获取或者设置 国名 /// </summary> public string Name { get; set; } /// <summary> /// 获取或者设置 皇帝 /// </summary> public string Emperor { get; set; } /// <summary> /// 获取或者设置 所选课程列表 /// </summary> public List<FamousGeneral> Courses { get; set; } public override string ToString() { return $"三国之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大将"; } } /// <summary> /// 三国名将 /// </summary> public class FamousGeneral { /// <summary> /// 获取或者设置 编号 /// </summary> public int ID { get; set; } /// <summary> /// 获取或者设置 名字 /// </summary> public string Name { get; set; } /// <summary> /// 获取或者设置 描述 /// </summary> 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<FamousGeneral> { 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); T Deserialize<T>(byte[] arr); } public class ThreeCountries : ISerializeInterface { public byte[] Serialize<T>(T t) { // 将上面的序列化代码写在这 } T Deserialize<T>(byte[] arr) { // 将上面的反序列化代码写在这,不好意思我没写 } }
3. 本文操作
写了半天的Demo,文章可能就写的有点水了,我估计读者也不会仔细看代码,直接去Github check项目去了,哈哈。
我还是简单说说吧,实现很简单,定义一些特性,下面红框里的代码文件:
使用很简单,在上面的数据类上加上特性,改动不多,看下面代码:
/// <summary> /// 三国 /// </summary> [NetObject(Name = "ThreeCountries", Version = 1)] public class ThreeCountries { /// <summary> /// 获取或者设置 ID /// </summary> [NetObjectProperty(ID = 1)] public int ID { get; set; } /// <summary> /// 获取或者设置 国名 /// </summary> [NetObjectProperty(ID = 2)] public string Name { get; set; } /// <summary> /// 获取或者设置 皇帝 /// </summary> [NetObjectProperty(ID = 3)] public string Emperor { get; set; } /// <summary> /// 获取或者设置 所选课程列表 /// </summary> [NetObjectProperty(ID = 4)] public List<FamousGeneral> Courses { get; set; } public static NetObjectAttribute CurrentObject = null; static ThreeCountries() { CurrentObject = NetObjectSerializeHelper.GetAttribute<ThreeCountries, NetObjectAttribute>(default(ThreeCountries)); } public override string ToString() { return $"三国之一{ID}:{Name}皇帝{Emperor},有 {Courses.Count}名大将"; } } /// <summary> /// 三国名将 /// </summary> public class FamousGeneral { /// <summary> /// 获取或者设置 编号 /// </summary> [NetObjectProperty(ID = 1)] public int ID { get; set; } /// <summary> /// 获取或者设置 名字 /// </summary> [NetObjectProperty(ID = 2)] public string Name { get; set; } /// <summary> /// 获取或者设置 描述 /// </summary> [NetObjectProperty(ID = 3)] public string Memo { get; set; } public override string ToString() { return $"{ID}:{Name}=>{Memo}"; } }
仔细看的话,只在外层类(ThreeCountries)上加了NetObject特性,和属性上加了NetObjectProperty特性,分别标识消息名称、版本号及每个属性的序列化与反序列化顺序即可,类中使用的子对象Courses属性,也只需要加属性特性即可,如上。
下面添加单元测试,并且测试通过:
4. 总结
用这套代码(demo,有所改变,但也差不多),完成了几个类似的项目,每次数据通信联调、测试问题,C++和java的同事找我时,我就说:
"你先看你自己数据包的序列化和反序列化代码有没有问题,我这不会出问题的,完全按数据包格式转的。"
刚开始还在那闹,后面定位几次问题后,类似的问题他们就没再找我了,偷笑中。
源码:见开源项目TerminalMACS。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
View绘制系列(9)-Canvas太极图绘制
Canvas太极图绘制 前面我们已经学习了Path.quadTo(float x1, float y1, float x2, float y2)及Path.cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)方法的使用,但并不是所有的曲线所有的曲线都需要用贝塞尔曲线来描述,毕竟在没有专业软件辅助的情况下,确认控制点也是一件很复杂的事情,比如说我们闭合曲线中包含一段椭圆弧或者圆弧,抑或者圆角矩形,我们该怎么做呢?作为描述组合路径的核心类,Path当然会提供对应的方法。 Path部分路径截取函数对照说明表: 函数名 函数说明 备注 addArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle) 添加以(left,top)为左上顶点,(right,bottom)为右下顶点矩形的内切椭圆中,以startAngle角度起始,划过sweepAngle角度后所得到的弧 注意:这里传入的startAngl...
- 下一篇
如何优雅统计订单收益(一)
引言 统计订单收益是做电商类型的APP老生常谈的问题.常规需求大致有用户收益日报/月报/年报.这些报表型的数据对表设计和程序设计有着不小的挑战.常规的聚合查询语句的查询时间会随着收益表数据日渐庞大而逐渐变长.这时候就需要思考如何设计收益表可以更高效的查询?怎样的设计才可以让统计收益变得简单? 需求 效果图 具体需求 1.收益类型分为:自购订单收益,分享订单收益,分销收益,活动收益 2.统计当日收益,当月收益 3.根据筛选的时间统计出时间段的收益. 思考 设计思路 订单表是肯定需要的.在写入或者修改订单表的时候同步写入修改收益表.只有自购和分享订单会记录到订单表中,分销以及活动赠送收益只在特殊业务中写入收益表.再以日为维度,创建一张用户收益日报表.单行记录写入用户当天收益情况.降低查询用户日/月/年收益统计时的数据量.以单用户为例,通过拆分用户一个月只会产生最多31条数据.属于可控增长速度.如果沿用收益表,因为收益表的数据量跟用户下单的数量一一对应,如果用户下单量多那么表会非常庞大.在前期用户量初见增长时,可用此方法规避大的数据量统计,后期如果用户量增大导致日报表数据变多可以再考虑分表....
相关文章
文章评论
共有0条评论来说两句吧...