ActionContract - ScutGame/Scut GitHub Wiki
此章节介绍如何定义客户端与服务端通讯协议
客户端与服务端之间的数据传输,需要定义一定的数据结构,Scut提供了两种方案。
Scut的通讯协议
Scut提供一套二进制的通讯协议,拥有传输小,可扩展,跨平台,向下兼容等特点。
请求结构
它与Http Get的参数格式相同(如:Key1=Val1&Key2=Val2)。
消息结构
Get Params | [双换行符 | 扩展数据流]
请求的流分两部分:
- 第一部分是字符串的格式流(命名为Get Params)
-
参数之间是无顺序的;
-
必需参数:MsgId、ActionId、Sid、Uid、St和sign;在协议平台工具中不需要再定义
- MsgId:Int类型,表示请求的消息编号,每次请求时此编号由客户端发送时自增,客户端接收响应时根据此Id识别所属哪个请求的响应消息流;
- ActionId:Int类型,表示请求需要指定哪个Action处理请求,特定值:1为心跳包请求编号,2为连接中断请求编号,1000以下编号提供给Scut引擎预留使用;
- Sid:String类型,表示请求的Session会话编号,可以为空,在客户端首次请求时会创建一个唯一的Session编号(Guid类型),客户端登录身份认证通过后服务端返回给客户端,之后的请求需要客户端将此编号通过Sid参数上传给服务端,若Sid参数为空需要重新登录身份认证;
- Uid:Int类型,表示登录身份认证的用户ID,未认证时为0,身份认证后由服务端返回给客户端,之后的请求需要同sid参数一样必需提供值,否则需要重新登录身份认证;
- St: String类型,表示时间缀字串,防止恶意的请求,不启用时为默认值"st";
- sign:String类型,表示MD5的签名字串,防止恶意的请求,以请求的所有参数字串+密钥串(32位)进行MD5较验,较验不通过的请求则不处理,密钥串客户端与服务端保持相同配置;
- 所有参数串需要通过UrlEncode进行编码,在放入d参数中提交请求,格式如下:
Get Params: “?d=UrlEncode({Paramstr})”
{Paramstr}变量:“MsgId=1&ActionId=1005&Sid=&Uid=9999&St=st&sign=xxxxxxxx”
- 第二部分是扩展数据流,可选
支持上传些文件流或图片流等,与第一部分之间需要以两个换行(\r\n\r\n)符分隔,协议工具平台不能定义;
客户端合并字节GetBytes("?d=MsgId=1&ActionId=1005&Sid=&Uid=9999&St=st&sign=xxxxxxxx") + GetBytes("\r\n\r\n") + GetBytes(图片的)的方式提交请求数据,服务器端通过HttpGet.InputStreamBytes属性可以获得客户端上传的图片流数据。
客户端调用示例: ```csharp
public void DoHttpUpLoad()
{
var paramBytes = Encoding.ASCII.GetBytes("?d=MsgId%3D0%26Sid%3D%26Uid%3D0%26ActionID%3D404%26St%3D%26errorinfo%3D%26sign%3Db71755ec1556fb951ad507e59bd98873\r\n\r\n");
var imgBytes = new byte[] { 123, 132, 132, 132, 12, 3 };
var data = new byte[paramBytes.Length + imgBytes.Length];
Buffer.BlockCopy(paramBytes, 0, data, 0, paramBytes.Length);
Buffer.BlockCopy(imgBytes, 0, data, paramBytes.Length, imgBytes.Length);
var response = HttpUtils.Post("http://192.168.1.100:9001/Service.aspx", data, null, null, null, null);
var reader = new BufferReader(response.GetResponseStream());
Trace.WriteLine("Response len:" + reader.Data.Length);
}
```
协议定义示例:排行榜录入接口
/*不用要求顺序*/
UserName | String
Score | Int
开发者注意:
- 启用Http服务端,Url示例:http://localhost:2015/Service.aspx?d=MsgId%3D1%26Sid%3D%26Uid%3D9999%26ActionID%3D1005%26sign%3D280a8761656463aa0a6d9eab09a0b036
- 启用Socket服务端, 需要增加包的长度作为粘拆包处理,格式:参数串的长度 + 参数串[Get Params]
响应结构
Http和Socket都使用相同的二进制流返回给客户端,支持byte、bool、short、int、long、ushort、uint、ulong、float、double、date、string等基础数据类型;WebSocket以Json格式字串返回。
通过将基础类型转换为Byte[]有序拼接起来,除String类型外其它的类型转成Byte的位数都是固定的(如:Bool占1位,Short占2位,Int占4位,Long占8位等),String类型加4位Int表示内容长度;客户端通过协议工具定义的接口读流按位解析。示例:
Int: 1000
结果: [232,3,0,0]
消息结构:(注:Fields和Record之间也是有序的)
Head | Fields[Field1,Field2,...] | Record[Record1,Record2,...]
- Head:表示固定的响应头,Fields包括如下:
ErrorCode | MsgId | ErrorInfo | ActionId | St
/*
ErrorCode:表示错误代码,0为成功
ErrorInfo:表示错误描述,可以为空
MsgId:请求的MsgId编号,如果是服务器主动Send时为0
*/
-
Fields: 基础数据类型数据字段集合,是有序的,保证协议兼容,新增的Field放在结尾;
-
Record: 记录行结构,可以并行多个,子结构可以嵌套,以Record和End标记Field列的区间范围,
Record结构:
RecordCount | [RecordLen1 | [Field1,Field2,...], RecordLen2 | [Field1,Field2,...]]
- RecordCount:表示记录的行数,Int类型;
- RecordLen:表示单行记录的数据字节长度;
- Field1: 表示有序的Field字段,最小单元元素;
协议定义示例:排行榜列表接口
/*要求顺序,增加要放到结尾,以下是响应参数部分*/
Int | PageCount | 分页总页数
-------------------
Record | | Record结构,标记Field列范围开始位置
String | UserName |
Int | Score |
End | | Record结构,标记Filed列范围结束位置
-------------------
多个Record并行结构
Int | Field1
Int | Field2
-------------------
Record | Record1
String | item1
Int | item2
End
-------------------
Record | Record2
String | item3
Int | item4
End
-------------------
Int | Field3
Int | Field4
多个Record嵌套结构
Int | Field1
Int | Field2
-------------------
Record | Record1
String | item1
Int | item2
-----------------
Record | Record2
String | item3
Int | item4
End
-----------------
End
-------------------
Int | Field3
Int | Field4
消息包结构
请求结构和响应结构的流都需要增加消息内容长度(Int占4位),在粘包拆包处理时读取此长度,再读取后续的长度流,拆分成多个完整消息包。
Http和Socket协议有些差别,如下:
- Http格式
消息内容长度 | 消息内容
- Socket格式
消息内容长度 | 消息内容长度 | 消息内容
- Socket流带Gzip压缩:Gzip压缩判断头4位是否是(16进制):[1F 8B 08 00]
消息内容长度 | Gzip压缩(消息内容长度 | 消息内容)