我们在之前写的NetManager,属于消息发送的底层工具类,它提供各种消息的收发接口,协议收发需要另外一个单独的工具类。

ProtocolManager

在Unity工程的Scripts——Net文件夹里,新建ProtocolManager

我们先实现一下获取私钥的协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProtocolManager 
{
/// <summary>
/// 请求私钥
/// </summary>
public static void SecretRequest()
{
MsgSecret msg = new MsgSecret();
NetManager.Instance.AddProtoListener(ProtocolEnum.MsgSecret, (secMsg) =>
{
MsgSecret msgSecret = secMsg as MsgSecret;
NetManager.Instance.SetKey(msgSecret.Secret);
});
NetManager.Instance.SendMessage(msg);

}
}

私钥是客户端一开始链接服务器就要请求的,我们把获取私钥的请求放在NetManager.ConnectCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// <summary>
/// 链接回调
/// </summary>
/// <param name="ar">回调的状态</param>
void ConnectCallback(IAsyncResult ar)
{
try
{
//...
m_IsConnecting = false;
ProtocolManager.SecretRequest();//一定要写在m_IsConnecting后面。
Debug.Log("Socket Connect Success");
//...
catch (SocketException se)
{
//...
}
}

NetTest

新建一个NetTest的Mono脚本并挂载,用来测试网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;

public class NetTest : MonoBehaviour
{
void Start()
{
NetManager.Instance.Connect("127.0.0.1", 8011);
}

void Update()
{
NetManager.Instance.Update();
}
private void OnApplicationQuit()
{
NetManager.Instance.CloseConnection();
}
}

心跳包协议

客户端:

  1. 每隔一段时间发送MsgPing
  2. Msg处理线程内,每收到一个服务端MsgPing,更新客户端的lastPongTime
  3. Ping线程内,长期没有收到服务端的消息就断开连接。

服务端:

  1. 每收到一个客户端的消息,更新此客户端的lastPingTime
  2. 在while循环内每次比较所有客户端没有Ping过的时长。
  3. 长期没有Ping的客户端断开连接

NetManager中添加PingThread方法,并在连接成功的回调中开启这个线程

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
   /// <summary>
/// 链接回调
/// </summary>
/// <param name="ar">回调的状态</param>
void ConnectCallback(IAsyncResult ar)
{
try
{
//...
m_IsConnecting = false;
m_HeartThread = new Thread(PingThread);//+++
m_HeartThread.IsBackground = true;//+++
m_HeartThread.Start();//+++
ProtocolManager.SecretRequest();
Debug.Log("Socket Connect Success");
//...
}
catch (SocketException se)
{
//...
}
}

void PingThread()
{
while(m_Socket != null && m_Socket.Connected)
{
long timeNow = GetTimeStamp();
if (timeNow - lastPingTime > m_PingInterval)
{
MsgPing msgPing = new MsgPing();
SendMessage(msgPing);
lastPingTime = GetTimeStamp();
}
//如果心跳包过长时间没收到,就关闭连接
if(timeNow - lastPongTime > m_PingInterval * 4)
{
CloseConnection(false);
}
}
}

服务器发送心跳包

修改服务器的SysMsg(就是之前命名为MsgSecret的文件,如果没改请改名),添加MsgPing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[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;
}
[ProtoBuf.ProtoContract()]
public class MsgPing : MsgBase
{
public MsgPing()
{
ProtoType = ProtocolEnum.MsgPing;
}
[ProtoBuf.ProtoMember(1)]
public override ProtocolEnum ProtoType { get; set; }
}

修改服务器的MsgHandler,添加MsgPing方法

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
namespace SimpleServer.Net
{
public partial class MsgHandler
{
/// <summary>
/// 分发私钥的请求命令
/// </summary>
/// <param name="client">请求的客户端</param>
/// <param name="msg">请求的msg</param>
public static void MsgSecret(ClientSocket client, MsgBase msg)
{
if (msg == null)
{
Debug.LogError(client.Socket?.RemoteEndPoint?.ToString() + "MsgSecret协议接收错误!");
}
else
{
MsgSecret msgSecret = (MsgSecret)msg;
msgSecret.Secret = ServerSocket.SecretKey;
ServerSocket.Send(client, msgSecret);
}
}
/// <summary>
/// 分发心跳包
/// </summary>
public static void MsgPing(ClientSocket client, MsgBase msg)
{
if (msg == null)
{
Debug.LogError(client.Socket?.RemoteEndPoint?.ToString() + "MsgPing协议接收错误!");
}
client.LastPingTime = ServerSocket.GetTimeStamp();
MsgPing msgPong = new MsgPing();
ServerSocket.Send(client, msgPong);
}
}
}

服务器维护的client的LastPingTime字段,只有接收到client的心跳包时才会被更新。