SuperSocket使用 IRequestInfo 和 IReceiveFilter 等对象实现自定义协议
为什么你要使用自定义协议?
通信协议用于将接收到的二进制数据转化成您的应用程序可以理解的请求。 SuperSocket提供了一个内置的通信协议“命令行协议”定义每个请求都必须以回车换行"\r\n"结尾。
但是一些应用程序无法使用命令行协议由于不同的原因。 这种情况下,你需要使用下面的工具来实现你的自定义协议:
* data class
* RequestInfo
* ReceiveFilter
* ReceiveFilterFactory
* AppServer and AppSession
第一步、定义一个和协议合适的数据结构
数据结构是通信数据存储的结构形式,也是自定义协议的基础,每个客户端发送数据最后都解析到此数据结构(使用"简体中文(GB2312)",Encoding.GetEncoding("gb2312"编码)
)
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"的字符串类型的属性:
根据你的应用程序的需要来定义你自己的请求类型。 例如:
public class MyRequestInfo : RequestInfo<MyData>
{
public MyRequestInfo(string key, MyData myData)
{
//如果需要使用命令行协议的话,那么key与命令类名称myData相同
Initialize(key, myData);
}
}
第三步、接收过滤器(ReceiveFilter)
接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo)。
实现一个接收过滤器(ReceiveFilter), 你需要实现接口 IReceiveFilter:
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" 是你要在整个程序中使用的请求类型
/// <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 来使用他们.
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())
{
}
}
第六步、服务端使用实例
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;
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");
}
SuperSocket使用 IRequestInfo 和 IReceiveFilter 等对象实现自定义协议的更多相关文章
- 使用SuperSocket实现自定义协议C/S设计
一.简介: 21世纪是出于互联网+的时代,许多传统行业和硬件挂钩的产业也逐步转向了系统集成智能化,简单来说就是需要软硬件的结合.这时,软硬件通讯便是这里面最主要的技术点,我们需要做到的是让硬件能够听懂 ...
- JSON对象(自定义对象)
JSON对象(自定义对象) 1.什么是JSON对象 JSON对象是属性的无序集合,在内存中也表现为一段连续的内存地址(堆内存) 1)JSON对象是属性的集合 2)这个集合是没有任何顺序的 2.JSON ...
- Unit07: document 对象 、 自定义对象 、 事件
Unit07: document 对象 . 自定义对象 . 事件 知识点: <!DOCTYPE html> <html> <head> <meta chars ...
- php 对象的自定义遍历
php对象的自定义遍历 对手册中的案例进行分析 更好的理解foreach() 的遍历步骤 class myIterator implements Iterator { private $positio ...
- 对象数组自定义排序--System.Collections.ArrayList.Sort()
使用System.Collections.ArrayList.Sort()对象数组自定义排序 其核心为比较器的实现,比较器为一个类,继承了IComparer接口并实现int IComparer.Com ...
- 【vue】@click绑定的函数,如何同时传入事件对象和自定义参数
知识很久不用的话,果然是容易忘的... 记记笔记,希望能加深点印象吧. [仅仅传入事件对象] html: <div id="app"> <button @clic ...
- SuperSocket 1.6.4 通过FixedHeaderReceiveFilter解析自定义协议
SuperSocket 提供了一些通用的协议解析工具, 你可以用他们简单而且快速的实现你自己的通信协议: TerminatorReceiveFilter (SuperSocket.SocketBase ...
- Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据
可以通过“关联对象”机制来把两个对象连起来 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系” 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于 ...
- 给内置对象或自定义对象添加存取器属性(getter setter)的方法总结
funct = { get: function() { return this._x }, set: function(value) { this._x = value } } function Ob ...
随机推荐
- php设计模式课程---6、策略模式如何使用
php设计模式课程---6.策略模式如何使用 一.总结 一句话总结:比如代码需求,做一饭店,有南北方不同菜系,不同分店有不同的饭菜汤的需求,代码怎么设计 从饭店有特色过渡到厨师有特色(南方厨师(南方饭 ...
- Linux课程---7、shell技巧(获取帮助命令)
Linux课程---7.shell技巧(获取帮助命令) 一.总结 一句话总结: ls --help:简单手册 man ls:内容手册 1.tab补全? 命令+tab:加快敲命令敲文件目录的速度,多敲几 ...
- pom详解
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- Python--基础文件读写操作
1,open(),对文件进行读写操作之前,要先打开文件,获取文件的句柄: 懒人专用方法,文件打开后不用关闭 with open(r'somefile.txt','r+',encoding='utf8' ...
- Mybatis_学习_00_资源帖
源码:https://github.com/mybatis/mybatis-3 一.官方 (1)Mybatis官方文档中文版 (2)MyBatis 从入门到精通 书中指定的网络资源 (3)MyBat ...
- Java企业微信开发_01_接收消息服务器配置
一.准备阶段 需要准备事项: 1.一个能在公网上访问的项目: 见:Java微信公众平台开发_01_本地服务器映射外网 2.一个企业微信账号: 去注册:(https://work.weixin.qq.c ...
- 【二叉树的递归】07路径组成数字的和【Sum Root to Leaf Numbers】
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 给定一个二叉树,节点的值仅限于从0 ...
- bzoj 2969: 矩形粉刷 概率期望
题目: 为了庆祝新的一年到来,小M决定要粉刷一个大木板.大木板实际上是一个W*H的方阵.小M得到了一个神奇的工具,这个工具只需要指定方阵中两个格子,就可以把这两格子为对角的,平行于木板边界的一个子矩形 ...
- nvidia-docker 安装
1.安装docker 官方网址安装说明 https://docs.docker.com/install/linux/docker-ce/ubuntu/ 2.ubuntu 14.04/16.04/18. ...
- JS上传图片-通过FileReader获取图片的base64
下面文章,我想要的是: FileReader这个对象,可以借助FileReader来获取上传图片的base64,就可以在客户端显示该图片了.同时,还可以把该图片的base64发送到服务端,保存起来. ...