【实战】Unity3d实战之Unity3d网络游戏实战篇(12):客户端网络模块

Unity3d实战之Unity3d网络游戏实战篇(12):客户端网络模块

学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍,如有侵权请联系,必删。

 客户端的网络部分需要处理的数据并没有服务端那么多,要做的事情还是有相似之处的。流程:
 异步Socket接收消息 -> 消息按顺序存入消息列表 -> 依次处理先接收到的消息 -> 根据消息类型执行不同的逻辑。
 注意,客户端给服务端发送数据后,服务端会返回相应的处理结果,我们通过建立监听表来实现对返回消息的监听,当客户端发送数据后,注册相应的监听事件,当接收到对应的返回消息时,调用相应的处理方法。监听表分两类,一种是永久监听,一种是一次性监听。但是,有时候我们接收到返回消息时需要调用多个处理方法,我们可以通过使用委托来实现。
【实战】Unity3d实战之Unity3d网络游戏实战篇(12):客户端网络模块_第1张图片
【实战】Unity3d实战之Unity3d网络游戏实战篇(12):客户端网络模块_第2张图片
 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方法在打包消息前注册一次监听事件,默认监听的消息协议名和要发送的消息协议名相同。

以上。

你可能感兴趣的:(实战集,Unity3d)