在上一节我们将消息都添加到了列表当中,这一节我们将处理列表中的消息。

一般情况下,列表中的消息分为两类,一类是需要游戏前台处理的消息,一类是需要后台处理的。后台处理的消息包括心跳包等,因为这类消息当玩家不玩游戏转到别的软件时需要持续处理。

我们在NetManager里面开启两个线程,一个是消息处理线程,一个是心跳包线程,消息处理线程负责区分前台消息和后台消息。

1
2
private Thread m_MsgThread;
private Thread m_HeartThread;

消息处理线程

在客户端成功连接服务器时,初始化m_MsgThread,所以在ConnectCallback中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <summary>
/// 链接回调
/// </summary>
/// <param name="ar">回调的状态</param>
void ConnectCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
socket.EndConnect(ar);
FirstEvent(NetEvent.ConnectSucc, "");
m_MsgThread = new Thread(MsgThread);//+++回调方法一会儿添加
m_MsgThread.IsBackground = true;//+++
m_MsgThread.Start();//+++
m_IsConnecting = false;
//...
}
catch (SocketException se)
{
//...
}
}

将客户端和服务器Proto文件夹的MsgSecret.cs的名称修改为SysMsg.cs,它是一个总的消息文件。然后在里面添加客户端心跳包的协议。心跳包内部不包含任何信息,服务器和客户端都是自己记录时间。

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

客户端也要记录自己的时间戳,先在NetManager里面添加lastPongTime(最后一次从服务器接收的消息的时间)和lastPingTime(最后一次从服务器发送消息的时间)

1
2
static long lastPingTime;
static long lastPongTime;

然后和服务器一样在客户端里面添加获取时间戳的函数

1
2
3
4
5
public static long GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds);
}

消息处理线程分拣出来的前台消息需要Unity处理,所以我们还需要在NetManager添加前台消息的列表

1
private List<MsgBase> m_UnityMsgList;

InitState中都初始化好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// 初始化链接状态
/// </summary>
void InitState()
{
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_ReadBuff = new ByteArray();
m_IsConnecting = false;
m_IsClosing = false;
m_MsgList = new List<MsgBase>();
m_UnityMsgList = new List<MsgBase>();//+++
m_MsgCount = 0;
lastPingTime = GetTimeStamp();//+++
lastPongTime = GetTimeStamp();//+++
}

终于,我们在NetManager添加MsgThread回调方法,在异步线程处理消息,注意先处理后台,再处理前台

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
void MsgThread()
{
while (m_Socket != null && m_Socket.Connected)
{
if (m_MsgList.Count <= 0) continue;
MsgBase msgBase = null;
lock (m_MsgList)
{
if (m_MsgList.Count > 0)
{
msgBase = m_MsgList[0];
m_MsgList.RemoveAt(0);
}
}
if(msgBase != null)
{
if (msgBase is MsgPing)//分发后台消息
{
lastPongTime = GetTimeStamp();
m_MsgCount--;//及时减去后台消息的计数
}
else
{
lock (m_UnityMsgList)//分发前台消息
{
m_UnityMsgList.Add(msgBase);
}
}
}
else
{
break;
}
}
}

Unity前台处理消息

我们在NetManager里面添加Update函数来交给Unity的生命周期,并且声明新的委托来发布消息。

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
public delegate void ProtoListener(MsgBase msg);
private Dictionary<ProtocolEnum, ProtoListener> m_ProtoListenerDic = new Dictionary<ProtocolEnum, ProtoListener>();
/// <summary>
/// 监听对应的协议,一个协议只能有一个监听
/// </summary>
/// <param name="protocolEnum"></param>
/// <param name="listener"></param>
public void AddProtoListener(ProtocolEnum protocolEnum, ProtoListener listener)
{
m_ProtoListenerDic[protocolEnum] = listener;
}
void FirstProto(ProtocolEnum protocolEnum, MsgBase msg)
{
if(m_ProtoListenerDic.ContainsKey(protocolEnum))
{
m_ProtoListenerDic[protocolEnum].Invoke(msg);
}
}
public void Update()
{
MsgUpdate();
}
void MsgUpdate()
{
if (m_Socket != null && m_Socket.Connected)
{
if (m_MsgCount == 0) return;
MsgBase msgBase = null;
lock(m_UnityMsgList)
{
if(m_UnityMsgList.Count > 0)
{
msgBase = m_UnityMsgList[0];
m_UnityMsgList.RemoveAt(0);
m_MsgCount--;
}
}
if(msgBase != null)
{
FirstProto(msgBase.ProtoType, msgBase);
}
}
}