CustomAction - ScutGame/Scut GitHub Wiki

此章节介绍如何定制客户端与服务端的通讯协议

定义通讯结构

如果使用Scut提供的协议不能满足你的开发需求时, Scut还提供了扩展自定义的协议结构,如现在常用Google的Protobuf序列化的二进制流或者Json。

约束条件

当你自定通讯消息结构时,请求的消息中需要能够获得以下必须参数,Scut的自定义协议需要依赖以下几个参数处理;

参数如下:

  • MsgId:数据包的自增序号
  • SessionId:客户端与服务器通讯的会话编号
  • ActionId:服务器用来分发请求具体处理的Action类
  • UserId:用户ID标识,6.7.9.7以后版本是【可选】参数,通过sessionId可以获得

传输安全性

根据需求可以对整个消息包进行加密传输,也可只对消息包(或消息头部分)加上自定Key进行MD5加密,作为其中一个Sign参数上传给服务器,服务器以相同的方式对消息包进行MD5加密来比对Sign的值是否一致。

可以参考Ranking Sample中的示例。

排行榜示例

消息结构

分为两段Byte数组,第一段Byte数组表示Protobuf序列化后的消息头,第二段表示Protobuf序列化后的消息主体,如下:

消息头长度(Int) + 消息头Head(Byte[]) + 消息主体Body(Byte[])

消息头结构

在收到客户端上传的数据后,消息头需要进行解包再分发到Action层处理,因此需要实现IActionDispatcher接口来扩展处理自定的解包算法逻辑,在里建立CustomActionDispatcher类。

定义消息头MessagePack类,代码如下:

[ProtoContract]
public class MessagePack
{
    [ProtoMember(1)]
    public int MsgId { get; set; }

    [ProtoMember(2)]
    public int ActionId { get; set; }

    [ProtoMember(3)]
    public string SessionId { get; set; }

    [ProtoMember(4)]
    public int UserId { get; set; }

    //以下扩展新属性
}

读取头部流的方法,代码如下:(ProtoBufUtils是Scut提供的Protobuf序列化工具类)

private MessagePack ReadMessageHead(byte[] data, out byte[] content)
{
    MessagePack headPack = null;
    content = new byte[0];
    try
    {
        //解头部(解之前当然还需要对byte[]解密,这里跳过这步)
        int pos = 0;
        byte[] headLenBytes = new byte[4];
        Buffer.BlockCopy(data, pos, headLenBytes, 0, headLenBytes.Length);
        pos += headLenBytes.Length;
        int headSize = BitConverter.ToInt32(headLenBytes, 0);
        if (headSize < data.Length)
        {
            byte[] headBytes = new byte[headSize];
            Buffer.BlockCopy(data, pos, headBytes, 0, headBytes.Length);
            pos += headBytes.Length;
            headPack = ProtoBufUtils.Deserialize<MessagePack>(headBytes);

            //解消息的内容
            if (data.Length > pos)
            {
                int len = data.Length - pos;
                content = new byte[len];
                Buffer.BlockCopy(data, pos, content, 0, content.Length);
                //内容数据放到具体Action业务上处理
            }
        }
        else
        {
            //不支持的数据格式
        }
    }
    catch (Exception ex)
    {
        //不支持的数据格式
    }
    return headPack;
}

消息体结构

消息体会根据Action处理需要的数据结构不同,因此是放在具体的Action内部再解包。

如在Action1001类的GetUrlElement方法解消息体包,代码如下:

public override bool GetUrlElement()
{
    byte[] data = (byte[])actionGetter.GetMessage();
    if (data.Length > 0)
    {
        requestPack = ProtoBufUtils.Deserialize<Request1001Pack>(data);
        return true;
    }
    return false;
}
        

Request1001Pack消息体代码如下:

[ProtoContract]
public class Request1001Pack
{
    [ProtoMember(101)]
    public int PageIndex { get; set; }

    [ProtoMember(102)]
    public int PageSize { get; set; }
}
    

使用自定解包类

上面我们已经实现好了自己的CustomActionDispatcher解包类,那怎么让它生效呢?

在MainClass类中配置GameEnvironment.Setting.ActionDispatcher属性即可,代码如下:

public class MainClass : GameHttpHost
{
    public MainClass()
    {
        GameEnvironment.Setting.ActionDispatcher = new CustomActionDispatcher();
    }

}
    
  • (:由于消息头与消息体结构需要提供给客户端(Untity3d)使用,因此我们将Request1001Pack类和MessagePack消息头独立到另一个类库项目)