KBEngine里的网络协议另类设计

5 篇文章 0 订阅

KBEngine里的网络协议另类设计

kb引擎本身和Unity通信是可以通过def文件进行网络协议定义的,生成C#代码也可以自动生成,但是每次通信协议变化需要更新C#代码,有没什么办法写一个通用方法呢

下面是我自己想办法整理的办法

办法1:
python可以把函数作为dict类型的value来使用,下面是个简单例子


def fun1(str):
    print(str)

def fun2(no):
    print(no)

#这里要注意dictFun的定义必须在函数的后面
dictFun = {
    1 : fun1,
    "2" : fun2,
    "3" : heroUpdate
    }


dictFun[1]("run fun1")
dictFun["2"](2)

执行结果
run fun1
2

这样我们就可以设计一个客户端函数例如:

def tos(str,parm)
tos("101",[数据1,数据2])

服务端接受后直接调用

dictFun.get("101")(parm)

def heroUpdate(data):
    idx = data[0]
    lv = data[1] #升到多少级
    

在函数中直接使用就好了。

这里我们发现几个不好的地方:
1,因为是全字符串发送,可能网络包体可能会变大;
2,因为你要定义101这样的协议编号,要维护这个稍微有点麻烦,有时候多发几个字节但是能提高开发速度也何乐而不为。
那么经过我的思考后,有了下面的方法2。

方法2:
按照不同变量类型来设计,并增加包头来解释包体,并包含服务端函数名称。
我们专门开一个ProtocolFun.py来定义所有的用户网络包协议。

import KBEngine
from KBEDebug import *

import struct

#处理网络协议
def ParseToServer(user , fun ,head, data):
    globals()[fun](user ,head, data)
    

#加入观战
def ToWatching(user ,head, data):
    try:
        a,b,c,d,e,f=struct.unpack(head,data)
        print("head:"+head)
        print("a:"+str(a))
        print("b:"+str(b))
        print("c:"+str(c))
        print("d:"+d.decode('utf-8'))
        print("e:"+str(e))
        print("f:"+str(f))
    except :
        ERROR_MSG("协议错误:"+"ToWatching(head):"+head)
        return
   

ParseToServer函数第一个user是Account的self,fun就是调用的函数,head就是数据包类型,data就是二进制数据。

客户端:

Account里的接受客户端函数,调用ProtocolFun的ParseToServer来进行调用

#客户端发给服务端信息
	def tos(self,fun,head,bytes):
		ProtocolFun.ParseToServer(self,fun,head,bytes)

在Unity客户端里,我们这样来发送数据,因为我们服务端代码是直接通过struct.unpack来解析的,我们可以在C#中写一个pack函数来打包,那么服务端就直接能解析了。在网络上搜索了一下,果然后有网友已经写了一个半成品(链接见下方参考),只要再补充一些自己需要的数据类型就好了,我经过修改增加了我要使用的数据类型,直接粘贴上来大家看看。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text.RegularExpressions;

// This is a crude implementation of a format string based struct converter for C#.
// This is probably not the best implementation, the fastest implementation, the most bug-proof implementation, or even the most functional implementation.
// It's provided as-is for free. Enjoy.

public class StructConverter
{
    //用来解析字符串前的数字的
    static string pattern = "\\d+";
    static Regex reg = new Regex(pattern);

    // We use this function to provide an easier way to type-agnostically call the GetBytes method of the BitConverter class.
    // This means we can have much cleaner code below.
    private static byte[] TypeAgnosticGetBytes(object o)
    {
        if (o is string) return System.Text.Encoding.UTF8.GetBytes((string)o);
        if (o is int) return BitConverter.GetBytes((int)o);
        if (o is uint) return BitConverter.GetBytes((uint)o);
        if (o is long) return BitConverter.GetBytes((long)o);
        if (o is ulong) return BitConverter.GetBytes((ulong)o);
        if (o is short) return BitConverter.GetBytes((short)o);
        if (o is ushort) return BitConverter.GetBytes((ushort)o);
        if (o is byte || o is sbyte) return new byte[] { (byte)o };
        throw new ArgumentException("Unsupported object type found");
    }

    private static string GetFormatSpecifierFor(object o,int len)
    {
        if (o is string) return len+"s";
        if (o is int) return "i";
        if (o is uint) return "I";
        if (o is long) return "q";
        if (o is ulong) return "Q";
        if (o is short) return "h";
        if (o is ushort) return "H";
        if (o is byte) return "B";
        if (o is sbyte) return "b";
        throw new ArgumentException("Unsupported object type found");
    }


    /// <summary>
    /// Convert a byte array into an array of objects based on Python's "struct.unpack" protocol.
    /// </summary>
    /// <param name="fmt">A "struct.pack"-compatible format string</param>
    /// <param name="bytes">An array of bytes to convert to objects</param>
    /// <returns>Array of objects.</returns>
    /// <remarks>You are responsible for casting the objects in the array back to their proper types.</remarks>
    public static object[] Unpack(string fmt, byte[] bytes)
    {
        Debug.WriteLine("Format string is length {0}, {1} bytes provided.", fmt.Length, bytes.Length);

        // First we parse the format string to make sure it's proper.
        if (fmt.Length < 1) throw new ArgumentException("Format string cannot be empty.");

        bool endianFlip = false;
        if (fmt.Substring(0, 1) == "<")
        {
            Debug.WriteLine("  Endian marker found: little endian");
            // Little endian.
            // Do we need to flip endianness?
            if (BitConverter.IsLittleEndian == false) endianFlip = true;
            fmt = fmt.Substring(1);
        }
        else if (fmt.Substring(0, 1) == ">")
        {
            Debug.WriteLine("  Endian marker found: big endian");
            // Big endian.
            // Do we need to flip endianness?
            if (BitConverter.IsLittleEndian == true) endianFlip = true;
            fmt = fmt.Substring(1);
        }

        // Now, we find out how long the byte array needs to be
        int totalByteLength = 0;

        //Regex r = new Regex("\\d+\\.?\\d*");
        
        
        //bool ismatch = reg.IsMatch(fmt);
        MatchCollection mc = reg.Matches(fmt);

        string newFmt = Regex.Replace(fmt, pattern, "");


        //bool bNum;  //本次是否数字
        //bool bStringNumber = false; //是否连续数字
        //string stringNumber = "";//字符串前的数字

        int stringNo = 0;

        char[] cs = newFmt.ToCharArray();
        foreach (char c in cs)
        {
            switch (c)
            {
                case 'q':
                case 'Q':
                    totalByteLength += 8;
                    break;
                case 'i':
                case 'I':
                    totalByteLength += 4;
                    break;
                case 'h':
                case 'H':
                    totalByteLength += 2;
                    break;
                case 'b':
                case 'B':
                case 'x':
                    totalByteLength += 1;
                    break;
                case 's':
                    totalByteLength += int.Parse(mc[stringNo].ToString()) ;
                    stringNo++;
                    break;
                default:
                    throw new ArgumentException("Invalid character found in format string : " +c);
            }

        }

        Debug.WriteLine("Endianness will {0}be flipped.", (object)(endianFlip == true ? "" : "NOT "));
        Debug.WriteLine("The byte array is expected to be {0} bytes long.", totalByteLength);

        // Test the byte array length to see if it contains as many bytes as is needed for the string.
        if (bytes.Length != totalByteLength) throw new ArgumentException("The number of bytes provided does not match the total length of the format string.head.Length:" + bytes.Length+ " - totalByteLength:" + totalByteLength);


        // Ok, we can go ahead and start parsing bytes!
        int byteArrayPosition = 0;
        List<object> outputList = new List<object>();
        byte[] buf;
        stringNo = 0;
        Debug.WriteLine("Processing byte array...");
        foreach (char c in cs)
        {
            switch (c)
            {
                case 'q':
                    outputList.Add((object)(long)BitConverter.ToInt64(bytes, byteArrayPosition));
                    byteArrayPosition += 8;
                    Debug.WriteLine("  Added signed 64-bit integer.");
                    break;
                case 'Q':
                    outputList.Add((object)(ulong)BitConverter.ToUInt64(bytes, byteArrayPosition));
                    byteArrayPosition += 8;
                    Debug.WriteLine("  Added unsigned 64-bit integer.");
                    break;
                case 'i':
                    outputList.Add((object)(int)BitConverter.ToInt32(bytes, byteArrayPosition));
                    byteArrayPosition += 4;
                    Debug.WriteLine("  Added signed 32-bit integer.");
                    break;
                case 'I':
                    outputList.Add((object)(uint)BitConverter.ToUInt32(bytes, byteArrayPosition));
                    byteArrayPosition += 4;
                    Debug.WriteLine("  Added unsignedsigned 32-bit integer.");
                    break;
                case 'h':
                    outputList.Add((object)(short)BitConverter.ToInt16(bytes, byteArrayPosition));
                    byteArrayPosition += 2;
                    Debug.WriteLine("  Added signed 16-bit integer.");
                    break;
                case 'H':
                    outputList.Add((object)(ushort)BitConverter.ToUInt16(bytes, byteArrayPosition));
                    byteArrayPosition += 2;
                    Debug.WriteLine("  Added unsigned 16-bit integer.");
                    break;
                case 'b':
                    buf = new byte[1];
                    Array.Copy(bytes, byteArrayPosition, buf, 0, 1);
                    outputList.Add((object)(sbyte)buf[0]);
                    byteArrayPosition++;
                    Debug.WriteLine("  Added signed byte");
                    break;
                case 'B':
                    buf = new byte[1];
                    Array.Copy(bytes, byteArrayPosition, buf, 0, 1);
                    outputList.Add((object)(byte)buf[0]);
                    byteArrayPosition++;
                    Debug.WriteLine("  Added unsigned byte");
                    break;
                case 'x':
                    byteArrayPosition++;
                    Debug.WriteLine("  Ignoring a byte");
                    break;
                case 's':
                    int len = int.Parse(mc[stringNo].ToString());
                    stringNo++;
                    string outstr = System.Text.Encoding.UTF8.GetString(bytes, byteArrayPosition, len);
                    outputList.Add(outstr);
                    byteArrayPosition += len;
                    break;
                default:
                    throw new ArgumentException("You should not be here. type :"+c);
            }
        }
        return outputList.ToArray();
    }

    /// <summary>
    /// Convert an array of objects to a byte array, along with a string that can be used with Unpack.
    /// </summary>
    /// <param name="items">An object array of items to convert</param>
    /// <param name="LittleEndian">Set to False if you want to use big endian output.</param>
    /// <param name="NeededFormatStringToRecover">Variable to place an 'Unpack'-compatible format string into.</param>
    /// <returns>A Byte array containing the objects provided in binary format.</returns>
    public static byte[] Pack(object[] items, bool LittleEndian, out string NeededFormatStringToRecover)
    {

        // make a byte list to hold the bytes of output
        List<byte> outputBytes = new List<byte>();

        // should we be flipping bits for proper endinanness?
        bool endianFlip = (LittleEndian != BitConverter.IsLittleEndian);

        // start working on the output string
        string outString = (LittleEndian == false ? ">" : "<");

        // convert each item in the objects to the representative bytes
        foreach (object o in items)
        {
            byte[] theseBytes = TypeAgnosticGetBytes(o);

            if (endianFlip == true) theseBytes = (byte[])theseBytes.Reverse();
            outString += GetFormatSpecifierFor(o, theseBytes.Length);
            outputBytes.AddRange(theseBytes);
        }

        NeededFormatStringToRecover = outString;

        return outputBytes.ToArray();

    }

    public static byte[] Pack(object[] items)
    {
        string dummy = "";
        return Pack(items, true, out dummy);
    }
}

调用函数Pack返回二进制数据,并通过参数NeededFormatStringToRecover返回了包体变量类型说明,是不是很方面呢,不用自己写了,这里再次感谢这位网友。

那么我们直接发送给服务端

	public void tos(string msgno, object[] parm)
    {
        string head;
        byte[] sendbyte = StructConverter.Pack(parm, true, out head);
        baseEntityCall.tos(msgno, head, sendbyte);
    }

	
	//调用方法
	object[] parm = { (uint)123456789, (int)987789, (int)4344, (string)"我的123", (int)555, (int)333 };
    Account.hero.tos("ToWatching", parm);

如何,这样我们来观察下数据

  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - head:<Iii9sii
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - a:123456789
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - b:987789
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - c:4344
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - d:我的123
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - e:555
  S_INFO    baseapp02 1200 7002  [2021-09-02 10:00:13 364] - f:333

收包同理去解析就好了

	public override void toc(string fun, string head,byte[] data)
    {
        object[] parm = StructConverter.Unpack(head, data);
    }

文章到这里就结束了,这样做的好处能加快开发速度,避免了网络包体解析头声明这部分工作量,缺点就是多了一个包体的head大小和函数名长度,大家不要命名太长了。如果你的某些包很频发发送,建议还是用kb引擎自带的协议来写能节省一些是一些。

参考:
Python 通过字符串调用函数或方法
https://segmentfault.com/a/1190000010476065

将字节串解读为打包的二进制数据
https://docs.python.org/zh-cn/3/library/struct.html

Equivalent in C# of Python’s “struct.pack/unpack”?
https://stackoverflow.com/questions/28225303/equivalent-in-c-sharp-of-pythons-struct-pack-unpack

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值