Unity【Multiplayer 多人在线】- Socket 通用服务端框架(三)、Protobuf 通信协议

举报
CoderZ1010 发表于 2022/09/25 05:13:47 2022/09/25
【摘要】 介绍         在阅读了罗培羽著作的Unity3D网络游戏实战一书后,博主综合自己的开发经验与考虑进行部分修改和调整,将通用的客户端网络模块和通用的服务端框架进行提取,形成专栏,介绍Socket网络编程,希望对其他人有所帮助。目录如下: &nbsp...

介绍

        在阅读了罗培羽著作的Unity3D网络游戏实战一书后,博主综合自己的开发经验与考虑进行部分修改和调整,将通用的客户端网络模块和通用的服务端框架进行提取,形成专栏,介绍Socket网络编程,希望对其他人有所帮助。目录如下:

   一、通用服务端框架

        (一)、定义套接字和多路复用​​​​​​

        (二)、客户端信息类和通用缓冲区结构

        (三)、Protobuf 通信协议

        (四)、数据处理和关闭连接

        (五)、Messenger 事件发布、订阅系统

        (六)、单点发送和广播数据

        (七)、时间戳和心跳机制

 二、通用客户端网络模块

        (一)、Connect 连接服务端

        (二)、Receive 接收并处理数据

        (三)、Send 发送数据

        (四)、Close 关闭连接

本篇内容:

1.Protobuf简介:

        Protocol Buffers是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。它不依赖于语言和平台并且可拓展性极强。

2.Protobuf优点:

同XML相比,Protobuf在序列化结构化数据方面有许多优点:

    *1.更简单

    *2.数据描述文件只需原来的1/10至1/3

    *3.解析速度是原来的20倍至100倍

    *4.减少了二义性

    *5.生成了更容易在编程中使用的数据访问类

    *6.支持多种编程语言

同Json相比,Protobuf的序列化速度也是提高了数倍

3.Protobuf语法规则:

1).指定字段类型

        所有的字段都是标量类型:string、bool、int类型等等,当然也可以为字段指定其他的合成类型,包括枚举或其他消息类型。

2).分配标识号

        在消息定义中,每个字段都有唯一的标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够更改。

        注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留[1,15]之内的标识号。

        切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。最小的标识号可以从1开始,最大到229 - 1,or 536,870,911。不可以使用其中的[19000-19999]标识号,Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。

3.)指定字段规则

所指定的消息字段修饰符必须是如下之一:

    * required : 不可增加或删除的字段,必须初始化;

    * optional : 可选字段,可删除,可以不初始化;

    * repeated : 可重复字段(对应C#里面的List);

4.编译工具protoc.exe:

1).创建.proto文件:

2).在控制台打开protoc.exe所在路径:

3).输入编译命令protoc -I=./ --csharp_out=./ .proto文件名称

生成的.cs文件:

5.ProtoUtility协议工具类:

将protobuf-net.dll加入项目引用:

封装ProtoUtility协议工具类,包含协议及协议名的编码与解码方法:


  
  1. using ProtoBuf;
  2. using System.Text;
  3. namespace SK.Framework.Sockets
  4. {
  5. /// <summary>
  6. /// 协议工具
  7. /// </summary>
  8. public static class ProtoUtility
  9. {
  10. /// <summary>
  11. /// 协议编码
  12. /// </summary>
  13. /// <param name="proto">协议</param>
  14. /// <returns>返回编码后的字节数据</returns>
  15. public static byte[] Encode(IExtensible proto)
  16. {
  17. using (MemoryStream ms = new MemoryStream())
  18. {
  19. Serializer.Serialize(ms, proto);
  20. return ms.ToArray();
  21. }
  22. }
  23. /// <summary>
  24. /// 协议解码
  25. /// </summary>
  26. /// <param name="protoName">协议名</param>
  27. /// <param name="bytes">要解码的byte数组</param>
  28. /// <param name="offset">协议体所在起始位置</param>
  29. /// <param name="count">协议体长度</param>
  30. /// <returns>返回解码后的协议</returns>
  31. public static IExtensible Decode(string protoName, byte[] bytes, int offset, int count)
  32. {
  33. using (MemoryStream ms = new MemoryStream(bytes, offset, count))
  34. {
  35. Type type = Type.GetType(protoName);
  36. return (IExtensible)Serializer.NonGeneric.Deserialize(type, ms);
  37. }
  38. }
  39. /// <summary>
  40. /// 协议名编码
  41. /// </summary>
  42. /// <param name="proto">协议</param>
  43. /// <returns>返回编码后的字节数据</returns>
  44. public static byte[] EncodeName(IExtensible proto)
  45. {
  46. //名字bytes和长度
  47. byte[] nameBytes = Encoding.UTF8.GetBytes(proto.GetType().FullName);
  48. Int16 length = (Int16)nameBytes.Length;
  49. //申请bytes数值
  50. byte[] bytes = new byte[length + 2];
  51. //组装2字节的长度信息
  52. bytes[0] = (byte)(length % 256);
  53. bytes[1] = (byte)(length / 256);
  54. //组装名字bytes
  55. Array.Copy(nameBytes, 0, bytes, 2, length);
  56. return bytes;
  57. }
  58. /// <summary>
  59. /// 协议名解码
  60. /// </summary>
  61. /// <param name="bytes">要解码的byte数组</param>
  62. /// <param name="offset">起始位置</param>
  63. /// <param name="length">长度</param>
  64. /// <returns>返回解码后的协议名</returns>
  65. public static string DecodeName(byte[] bytes, int offset, out int length)
  66. {
  67. length = 0;
  68. //必须大于2字节
  69. if (offset + 2 > bytes.Length) return string.Empty;
  70. //获取长度
  71. Int16 l = (Int16)((bytes[offset + 1] << 8) | bytes[offset]);
  72. if (l <= 0) return string.Empty;
  73. //长度必须足够
  74. if (offset + 2 + l > bytes.Length) return string.Empty;
  75. //解析
  76. length = 2 + l;
  77. string name = Encoding.UTF8.GetString(bytes, offset + 2, l);
  78. return name;
  79. }
  80. }
  81. }

参考资料:《Unity3D网络游戏实战》(第2版)罗培羽 著

文章来源: coderz.blog.csdn.net,作者:CoderZ1010,版权归原作者所有,如需转载,请联系作者。

原文链接:coderz.blog.csdn.net/article/details/124054972

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。