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