学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍,如有侵权请联系,必删。
客户端的网络部分需要处理的数据并没有服务端那么多,要做的事情还是有相似之处的。流程:
异步Socket接收消息 -> 消息按顺序存入消息列表 -> 依次处理先接收到的消息 -> 根据消息类型执行不同的逻辑。
注意,客户端给服务端发送数据后,服务端会返回相应的处理结果,我们通过建立监听表来实现对返回消息的监听,当客户端发送数据后,注册相应的监听事件,当接收到对应的返回消息时,调用相应的处理方法。监听表分两类,一种是永久监听,一种是一次性监听。但是,有时候我们接收到返回消息时需要调用多个处理方法,我们可以通过使用委托来实现。
1、建立MsgDistribution类管理消息列表和监听表。
用到的变量有:
public delegate void MsgDelegate(ProtocolBase protoBase);
public List msgList = new List();
public int maxMsgCount = 15;
private Dictionary<string, MsgDelegate> eventDict = new Dictionary<string, MsgDelegate> ();
private Dictionary<string, MsgDelegate> onceDict = new Dictionary<string, MsgDelegate> ();
其中:
MsgDelegate:委托类型,它能够引用其他的方法,跟C/C++中的函数指针类似,可以避免使用大量的if-else等语句的问题。但注意,引用的方法必须和MsgDelegate具有相同的参数和返回值类型;
msgList:消息列表,保存从服务端接收到的消息,先入先出原则;
maxMsgCount:每帧处理的最大消息数;
eventDict:永久性监听表,注册之后一直保持监听特定消息,key为监听的协议名,value为回调方法;
onceDict:一次性监听表,注册之后只监听一次,key为监听的协议名,value为回调方法;。
定义用于添加事件和删除事件的方法:
#region Extra Operation
///
/// listen a new item permanent unless it was deleted.
///
/// type string protoName
/// type MsgDelegate callback
public void AddListener(string protoName, MsgDelegate callback)
{
if (eventDict.ContainsKey (protoName)) {
eventDict [protoName] += callback;
} else {
eventDict [protoName] = callback;
}
}
///
/// listen a new item disposable or unless it was deleted.
///
/// type string protoName
/// type MsgDelegate callback
public void AddOnceListener(string protoName, MsgDelegate callback)
{
if (onceDict.ContainsKey (protoName)) {
onceDict [protoName] += callback;
} else {
onceDict [protoName] = callback;
}
}
///
/// delete an item permanent from permanent listener list.
///
/// type string protoName
/// type MsgDelegate callback
public void DelListener(string protoName, MsgDelegate callback)
{
if (eventDict.ContainsKey (protoName)) {
eventDict [protoName] -= callback;
if (eventDict [protoName] == null)
eventDict.Remove (protoName);
}
}
///
/// delete an item permanent from disposable listener list.
///
/// type string protoName
/// type MsgDelegate callback
public void DelOnceListener(string protoName, MsgDelegate callback)
{
if (onceDict.ContainsKey (protoName)) {
onceDict [protoName] -= callback;
if (onceDict [protoName] == null)
onceDict.Remove (protoName);
}
}
#endregion
处理消息分发时候,有可能会出现线程竞争的情况,因此,在对msdList进行操作的时候要注意使用lock,例如在分发掉一条消息后需要把该消息从消息列表移除时:
lock (msgList) {
msgList.RemoveAt (0);
}
2、建立Connection类实现消息收发
客户端的Connection和服务端的ServNetManager相似,需要实现对消息的监听和处理数据粘包分包,不一样的在于客户端的Connection主需要保持ReceiveCallback以及在接收到消息后,把消息插入消息列表即可。值得一提的是本书对Send方法进行了多次重载:
///
/// send a protocol to server.
///
/// type ProtocolBase
public bool Send(ProtocolBase protoBase)
{
byte[] bytes = protoBase.Encode ();
byte[] lenBytes = BitConverter.GetBytes (bytes.Length);
byte[] sendBytes = lenBytes.Concat (bytes).ToArray ();
try {
clientSk.Send(sendBytes);
Debug.Log("[Connection.Send] Send message: " + protoBase.GetName());
return true;
} catch (Exception ex) {
Debug.LogError ("[Connection.Send] cannot send message. " + ex.Message);
return false;
}
}
///
/// send a protocol to server and listen the server's return message with a specified cbName, at the same time, assign a callback function.
///
/// type ProtocolBase protoBase
/// type string cbName
/// type MsgDistribution.MsgDelegate callback
public bool Send(ProtocolBase protoBase, string cbName, MsgDistribution.MsgDelegate callback)
{
if (status != Status.Connected) {
Debug.LogError ("[Connection.Send] Connection has not established.");
return false;
}
msgDistribution.AddOnceListener (cbName, callback);
return Send (protoBase);
}
///
/// send a protocol to server and listen the server's return message. the specified cbName equal to protoName.
///
/// Proto base.
/// Callback.
public bool Send(ProtocolBase protoBase, MsgDistribution.MsgDelegate callback)
{
string protoName = protoBase.GetName ();
return Send (protoBase, protoName, callback);
}
第一个Send方法就是原始的给信息添加长度头部然后打包发送给服务端;
第二个Send方法在打包消息前注册一次性监听事件,传入的参数可以决定注册监听的协议名,当需要监听的消息协议名跟发送的消息协议名不同时,可以使用这个Send方法;
第三个Send方法在打包消息前注册一次监听事件,默认监听的消息协议名和要发送的消息协议名相同。
以上。