我们直接使用序列化后的ProtoBuff数据来通信,并且加密数据。

我们只使用简易的序列化,不使用.proto中间文件来映射类。

每个和客户端通信的协议都是一个类。具体到传输时,传输的协议内容分为三个部分,第一个部分是协议头部分,记录了整段信息的大小,第二个部分是协议名部分,协议名部分包含了协议名长度和具体的协议名,第三个部分才是具体的协议内容。

ProtocolEnum

使用一个枚举来表示协议类型。协议在传输时就是用一个数字表示的。

在Proto文件夹内新建ProtocolEnum文件

1
2
3
4
5
6
public enum ProtocolEnum
{
None = 0,
MsgSecret = 1,//密钥协议
MsgPing = 2,//心跳包协议
}

MsgBase

首先在Nuget包管理器中将ProtoBuf-net安装到SimpleServer工程内。

在Proto文件夹内新建MsgBase文件,这个是各种协议的基类,还提供了协议内容编解码的方法。

EncodeName方法,将协议名编码,将协议名按照UTF8编码的形式转化成byte数组之后,还要在前面加上协议名的长度,这个长度取两个byte,所以转化成Int16。协议名的部分没必要加密。记录协议名的长度是为了接收数据的时候方便计算后面具体协议内容的开始索引。

DecodeName方法,将协议名解码,并且输出协议名的部分占占消息内容的长度(也就是out int count)。

Encode方法,编码协议内容并加密,属于整个传输内容的第三部分。

Decode方法,解码协议内容,注意使用Serializer.NonGeneric.Deserialize来反序列化,使用GetType来获取到具体的协议类,否则的话无法成功反序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
using ProtoBuf;
using ServerBase;
using SimpleServer.Net;


public class MsgBase
{
public virtual ProtocolEnum ProtoType { get; set; }

/// <summary>
/// 编码协议名
/// </summary>
/// <param name="msg">协议名信息</param>
/// <returns>编码后的byte数组</returns>
public static byte[] EncodeName(MsgBase msg)
{
byte[] nameBytes = System.Text.Encoding.UTF8.GetBytes(msg.ProtoType.ToString());
Int16 len = (Int16)nameBytes.Length;//获取协议名的长度,转化为Int16
byte[] bytes = new byte[2 + len];
//Int16长度占2个byte,用两个byte将nameBytes的长度记录下来,供校验
bytes[0] = (byte)(len % 256);
bytes[1] = (byte)(len / 256);
Array.Copy(nameBytes,0,bytes, 2, len);
return bytes;
}
/// <summary>
/// 解码协议名
/// </summary>
/// <param name="bytes">协议头内容</param>
/// <param name="offset">偏移值(协议头开始读取的Index)</param>
/// <returns></returns>
public static ProtocolEnum DecodeName(byte[] bytes,int offset,out int count)
{
count = 0;
if(offset + 2 > bytes.Length) { return ProtocolEnum.None; }//说明bytes里面根本没有协议名
//bytes[offset + 1]是首位,所以左移8,bytes[offset]是末位
Int16 len = (Int16)((bytes[offset + 1] << 8) | bytes[offset]);
if(offset + 2 + len > bytes.Length) { return ProtocolEnum.None; }//不合理,len长度有问题
count = 2 + len;//这就是协议名及其大小占用的byte个数
try
{
string name = System.Text.Encoding.UTF8.GetString(bytes, offset + 2, len);
return (ProtocolEnum)Enum.Parse(typeof(ProtocolEnum), name);
}
catch (Exception e)
{
Debug.LogError("不存在的协议:" + e);
return ProtocolEnum.None;
}
}
/// <summary>
/// 编码协议内容并加密
/// </summary>
/// <param name="msg">协议信息</param>
/// <returns>编码后的byte数组</returns>
public static byte[] Encode(MsgBase msg)
{
using(MemoryStream ms = new MemoryStream())
{
//使用ProtoBuf将协议内容序列化
Serializer.Serialize(ms, msg);
byte[] bytes = ms.ToArray();
string secretkey = ServerSocket.SecretKey;
//将内容加密
//如果此协议是私钥协议,先使用公钥加密
if(msg is MsgSecret)
{
secretkey = ServerSocket.PublicKey;

}
bytes = AES.AESEncrypt(bytes, secretkey);
return bytes;
}
}
/// <summary>
/// 解密协议内容
/// </summary>
/// <param name="protocol">协议类型</param>
/// <param name="bytes">协议内容</param>
/// <param name="offset">ByteArray偏移值</param>
/// <param name="count">ByteArray需要读取的长度</param>
/// <returns>解密后的Msg</returns>
public static MsgBase? Decode(ProtocolEnum protocol,byte[] bytes,int offset,int count)
{
if (count <= 0)
{
Debug.LogError("协议解密出错,数据长度为0");
return null;
}
try
{
byte[] newbytes = new byte[count];
Array.Copy(bytes, offset, newbytes, 0, count);
string secretkey = ServerSocket.SecretKey;
if(protocol == ProtocolEnum.MsgSecret)
{
secretkey = ServerSocket.PublicKey;
}
newbytes = AES.AESDecrypt(newbytes, secretkey);
using (MemoryStream ms = new MemoryStream(newbytes,0,newbytes.Length))
{
Type? t = Type.GetType(protocol.ToString());
if (t != null)
{
//一定要先GetType然后再使用ProtoBuf反序列化,否则反序列化的只是基类不是子类
return (MsgBase)Serializer.NonGeneric.Deserialize(t, ms);
}
else
{
Debug.LogError("无法找到类型,请检查ProtocolEnum声明是否一致");
return null;
}
}
}
catch (Exception e)
{
Debug.LogError("协议解析出错:" + e);
return null;
}
}
}

MsgSecret

新建MsgSecret类,这代表了密钥的传输协议

1
2
3
4
5
6
7
8
9
10
11
[ProtoBuf.ProtoContract()]
public class MsgSecret : MsgBase
{
public MsgSecret() {
ProtoType = ProtocolEnum.MsgSecret;
}
[ProtoBuf.ProtoMember(1)]
public override ProtocolEnum ProtoType { get; set; }
[ProtoBuf.ProtoMember(2)]
public string? Secret;
}

注意:

  1. 每一个协议基类都必须要有一个构造函数和ProtoType的重载,否则ProtoBuf的序列化会出问题。
  2. 每一个协议基类的名称都要和ProtocolEnum内声明的名称相同。