SuperSocket使用 IRequestInfo 和 IReceiveFilter 等对象实现自定义协议
为什么你要使用自定义协议?
通信协议用于将接收到的二进制数据转化成您的应用程序可以理解的请求。 SuperSocket提供了一个内置的通信协议“命令行协议”定义每个请求都必须以回车换行"\r\n"结尾。
但是一些应用程序无法使用命令行协议由于不同的原因。 这种情况下,你需要使用下面的工具来实现你的自定义协议:
1
2
3
4
5
|
* data class * RequestInfo * ReceiveFilter * ReceiveFilterFactory * AppServer and AppSession |
第一步、定义一个和协议合适的数据结构
数据结构是通信数据存储的结构形式,也是自定义协议的基础,每个客户端发送数据最后都解析到此数据结构(使用"简体中文(GB2312)",Encoding.GetEncoding("gb2312"编码)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class MyData //20字节 { /// <summary> /// 开始符号6字节, "!Start" /// </summary> public string Start { get ; set ; } /// <summary> /// 消息类型,1字节 /// </summary> public byte key { get ; set ; } /// <summary> /// 主体消息数据包长度,4字节 /// </summary> public uint Lenght { get ; set ; } /// <summary> /// 4字节唯一设备识别符(Unique Device Identifier) /// </summary> public uint DeviceUDID { get ; set ; } /// <summary> /// 目标命令类型1字节 /// </summary> public byte Type { get ; set ; } /// <summary> /// 主体消息 /// </summary> public byte [] Body { get ; set ; } /// <summary> /// 结束符号4字节, "$End" , /// </summary> public string End { get ; set ; } public override string ToString() { return string .Format( "开始符号:{0},消息类型:{1},数据包长度:{2},唯一设备识别符:{3},目标命令类型:{4},主体消息:{5},结束符号:{6}" , Start, key, Lenght, Lenght, DeviceUDID, Type, Body, End); } } |
第二步、请求(RequestInfo)
RequestInfo 是表示来自客户端请求的实体类。 每个来自客户端的请求都能应该被实例化为 RequestInfo 类型。 RequestInfo 类必须实现接口 IRequestInfo,该接口只有一个名为"Key"的字符串类型的属性:
根据你的应用程序的需要来定义你自己的请求类型。 例如:
1
2
3
4
5
6
7
8
|
public class MyRequestInfo : RequestInfo<MyData> { public MyRequestInfo( string key, MyData myData) { //如果需要使用命令行协议的话,那么key与命令类名称myData相同 Initialize(key, myData); } } |
第三步、接收过滤器(ReceiveFilter)
接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo)。
实现一个接收过滤器(ReceiveFilter), 你需要实现接口 IReceiveFilter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public class MyReceiveFilter : IReceiveFilter<MyRequestInfo> { public Encoding encoding = Encoding.GetEncoding( "gb2312" ); /// <summary> /// Gets the size of the left buffer. /// </summary> /// <value> /// The size of the left buffer. /// </value> public int LeftBufferSize { get ; } /// <summary> /// Gets the next receive filter. /// </summary> public IReceiveFilter<MyRequestInfo> NextReceiveFilter { get ; } public FilterState State { get ; private set ; } /// <summary> /// 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。 /// </summary> /// <param name="readBuffer">接收缓冲区, 接收到的数据存放在此数组里</param> /// <param name="offset">接收到的数据在接收缓冲区的起始位置</param> /// <param name="length">本轮接收到的数据的长度</param> /// <param name="toBeCopied">表示当你想缓存接收到的数据时,是否需要为接收到的数据重新创建一个备份而不是直接使用接收缓冲区</param> /// <param name="rest">这是一个输出参数, 它应该被设置为当解析到一个为政的请求后,接收缓冲区还剩余多少数据未被解析</param> /// <returns></returns> /// 当你在接收缓冲区中找到一条完整的请求时,你必须返回一个你的请求类型的实例. /// 当你在接收缓冲区中没有找到一个完整的请求时, 你需要返回 NULL. /// 当你在接收缓冲区中找到一条完整的请求, 但接收到的数据并不仅仅包含一个请求时,设置剩余数据的长度到输出变量 "rest". SuperSocket 将会检查这个输出参数 "rest", 如果它大于 0, 此 Filter 方法 将会被再次执行, 参数 "offset" 和 "length" 会被调整为合适的值. public MyRequestInfo Filter( byte [] readBuffer, int offset, int length, bool toBeCopied, out int rest) { rest = 0; if (length < 21) //没有数据 return null ; byte [] data = new byte [length]; Buffer.BlockCopy(readBuffer, offset, data, 0, length); var str = encoding.GetString(data); MyData myData= new MyData(); myData.Start= encoding.GetString(data,0,6); //6字节 myData.key= data[6]; //1字节 myData.Lenght = BitConverter.ToUInt32(data, 7); //4字节 6 + 1 if (length < myData.Lenght + 20) return null ; myData.DeviceUDID= BitConverter.ToUInt32(data, 11); //4字节 6 + 1+4 myData.Type = data[15]; //1字节 6+1+4+4 myData.Body = new byte [myData.Lenght]; //myData.Lenght字节 Buffer.BlockCopy(data, 16, myData.Body, 0, ( int )myData.Lenght); myData.End= encoding.GetString(data, ( int )(16+ myData.Lenght), 4); //4字节 if (myData.Start != "!Start" || myData.End != "$End" ) return null ; rest =( int )(length-(20+ myData.Lenght)); //未处理数据 return new MyRequestInfo(myData.key.ToString(),myData); } |
public void Reset()
{
}
- TRequestInfo: 类型参数 "TRequestInfo" 是你要在程序中使用的请求类型(RequestInfo);
- LeftBufferSize: 该接收过滤器已缓存数据的长度;
- NextReceiveFilter: 当下一块数据收到时,用于处理数据的接收过滤器实例;
- Reset(): 重设接收过滤器实例到初始状态;
-
Filter(....): 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。
TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest);
- readBuffer: 接收缓冲区, 接收到的数据存放在此数组里
- offset: 接收到的数据在接收缓冲区的起始位置
- length: 本轮接收到的数据的长度
- toBeCopied: 表示当你想缓存接收到的数据时,是否需要为接收到的数据重新创建一个备份而不是直接使用接收缓冲区
- rest: 这是一个输出参数, 它应该被设置为当解析到一个为政的请求后,接收缓冲区还剩余多少数据未被解析
这儿有很多种情况需要你处理:
- 当你在接收缓冲区中找到一条完整的请求时,你必须返回一个你的请求类型的实例.
- 当你在接收缓冲区中没有找到一个完整的请求时, 你需要返回 NULL.
- 当你在接收缓冲区中找到一条完整的请求, 但接收到的数据并不仅仅包含一个请求时,设置剩余数据的长度到输出变量 "rest". SuperSocket 将会检查这个输出参数 "rest", 如果它大于 0, 此 Filter 方法 将会被再次执行, 参数 "offset" 和 "length" 会被调整为合适的值.
第四步、接收过滤器工厂(ReceiveFilterFactory)
接收过滤器工厂(ReceiveFilterFactory)用于为每个会话创建接收过滤器. 定义一个过滤器工厂(ReceiveFilterFactory)类型, 你必须实现接口 IReceiveFilterFactory. 类型参数 "TRequestInfo" 是你要在整个程序中使用的请求类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/// <summary> /// Receive filter factory interface /// </summary> /// <typeparam name="TRequestInfo">The type of the request info.</typeparam> public class MyReceiveFilterFactory : IReceiveFilterFactory<MyRequestInfo> { /// <summary> /// Creates the receive filter. /// </summary> /// <param name="appServer">The app server.</param> /// <param name="appSession">The app session.</param> /// <param name="remoteEndPoint">The remote end point.</param> /// <returns> /// the new created request filer assosiated with this socketSession /// </returns> //MyReceiveFilter<MyRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint); public IReceiveFilter<MyRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint) { return new MyReceiveFilter(); } } |
第五步、和 AppSession,AppServer 配合工作
现在, 你已经有了 RequestInfo, ReceiveFilter 和 ReceiveFilterFactory, 但是你还没有正式使用它们. 如果你想让他们在你的程序里面可用, 你需要定义你们的 AppSession 和 AppServer 来使用他们.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class MySession : AppSession<MySession, MyRequestInfo> { public uint DeviceUDID; protected override void HandleException(Exception e) { } } public class MyServer : AppServer<MySession, MyRequestInfo> { /// <summary> /// 使用自定义协议工厂 /// </summary> public MyServer() : base ( new MyReceiveFilterFactory()) { } } |
第六步、服务端使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
MyServer myServer = new MyServer(); Encoding encoding = Encoding.GetEncoding( "gb2312" ); //Setup the appServer if (!myServer.Setup(1990)) //Setup with listening port { MessageBox.Show( "Failed to setup!" ); return ; } //Try to start the appServer if (!myServer.Start()) { MessageBox.Show( "Failed to start!" ); return ; } myServer.NewSessionConnected += MyServer_NewSessionConnected; myServer.NewRequestReceived += MyServer_NewRequestReceived; |
1
2
3
4
5
6
7
8
9
|
private void MyServer_NewRequestReceived(MySession session, MyRequestInfo requestInfo) { var msg=encoding.GetString(requestInfo.Body.Body); } private void MyServer_NewSessionConnected(MySession session) { session.Send( "Welcome to SuperSocket Telnet Server" ); } |
- Python入门基础连载(4)控制流
- 济西站构建大数据运营网络,打造智能化列车加工厂
- 五位数终端收购的域名dongxiao.cn已启用
- 全球互联网发展进入“拐点”——展望下一代互联网
- 2 分钟论文:语音生成表情包背后的技术原理
- 享学课堂谈-Python程序员的常见错误
- 区块链技术,如何提升网络安全?
- 趣店推“大白汽车”业务 启用域名dabaiqiche.com
- 糖果吃了那么多,你真的知道比特币分叉是咋回事吗?
- 静息态网络拓扑传输认知任务信息
- MYSQL字符串截取总结:LEFT、RIGHT、SUBSTRING、SUBSTRING
- 如何用Python提取中文关键词?
- 工信部:将加大网络提速降费力度加快百兆宽带普及
- 人工智能AI(5):线性代数之矩阵、线性空间
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 玩转 PhpStorm 系列(五):代码模板篇
- 通过 PDO 扩展与 MySQL 数据库交互(上)
- 通过 PDO 扩展与 MySQL 数据库交互(下)
- 使用 Vue 3.0,你可能不再需要Vuex了
- 浅析小程序云原生数据库设计与应用
- 实战丨用云开发快速构建信息申报小程序
- Linux gdb使用基础
- redis设置过期时间
- Linux gcc使用基础
- TensorFlow-平面曲线拟合
- TensorFlow-VGG16模型复现
- 腾讯云语音识别之实时语音识别
- React + Dva + Antd+umi 实践
- 重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
- Android数据库存储模块封装,让操作记录更好用可复用