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

介绍

        在阅读了罗培羽著作的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协议工具类,包含协议及协议名的编码与解码方法:

using ProtoBuf;
using System.Text;

namespace SK.Framework.Sockets
{
    /// <summary>
    /// 协议工具
    /// </summary>
    public static class ProtoUtility
    {
        /// <summary>
        /// 协议编码
        /// </summary>
        /// <param name="proto">协议</param>
        /// <returns>返回编码后的字节数据</returns>
        public static byte[] Encode(IExtensible proto)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                Serializer.Serialize(ms, proto);
                return ms.ToArray();
            }
        }
        /// <summary>
        /// 协议解码
        /// </summary>
        /// <param name="protoName">协议名</param>
        /// <param name="bytes">要解码的byte数组</param>
        /// <param name="offset">协议体所在起始位置</param>
        /// <param name="count">协议体长度</param>
        /// <returns>返回解码后的协议</returns>
        public static IExtensible Decode(string protoName, byte[] bytes, int offset, int count)
        {
            using (MemoryStream ms = new MemoryStream(bytes, offset, count))
            {
                Type type = Type.GetType(protoName);
                return (IExtensible)Serializer.NonGeneric.Deserialize(type, ms);
            }
        }
        /// <summary>
        /// 协议名编码
        /// </summary>
        /// <param name="proto">协议</param>
        /// <returns>返回编码后的字节数据</returns>
        public static byte[] EncodeName(IExtensible proto)
        {
            //名字bytes和长度
            byte[] nameBytes = Encoding.UTF8.GetBytes(proto.GetType().FullName);
            Int16 length = (Int16)nameBytes.Length;
            //申请bytes数值
            byte[] bytes = new byte[length + 2];
            //组装2字节的长度信息
            bytes[0] = (byte)(length % 256);
            bytes[1] = (byte)(length / 256);
            //组装名字bytes
            Array.Copy(nameBytes, 0, bytes, 2, length);
            return bytes;
        }
        /// <summary>
        /// 协议名解码
        /// </summary>
        /// <param name="bytes">要解码的byte数组</param>
        /// <param name="offset">起始位置</param>
        /// <param name="length">长度</param>
        /// <returns>返回解码后的协议名</returns>
        public static string DecodeName(byte[] bytes, int offset, out int length)
        {
            length = 0;
            //必须大于2字节
            if (offset + 2 > bytes.Length) return string.Empty;
            //获取长度
            Int16 l = (Int16)((bytes[offset + 1] << 8) | bytes[offset]);
            if (l <= 0) return string.Empty;
            //长度必须足够
            if (offset + 2 + l > bytes.Length) return string.Empty;
            //解析
            length = 2 + l;
            string name = Encoding.UTF8.GetString(bytes, offset + 2, l);
            return name;
        }
    }
}

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

猜你喜欢

转载自blog.csdn.net/qq_42139931/article/details/124054972