SuperSocket基础(二)-----一个完整的SocketServer项目


由于时间关系未能及时更新,关于SuperSocket,对于初学者而言,一个SuperSock的Server真的不好写。官方文档写的很清晰,如何接受客户端发来的二进制报文并做响应的解析。下面就从一个完整的项目出发,记录SuperSocket的开发过程。

1、项目场景:现有十多个RTU设备,用来监测自来水管的压力和流量,需要将十多个传感器传来的值接收并做解析来使用。用SuperSocket写一个Socket服务器,实时监听客户端发来的数据报文。

具体的开发过程:

一、在vs中新建一个项目,Windows窗体或者控制台程序都可以,在项目解决方案中鼠标右键点击管理NuGet程序包,在线安装SuperSocket。

二、自定义自己服务器中相关的类,建议类的建立顺序:RequestInfo>ReceiveFilter>AppSession>AppServer

1、建立一个RequestInfo

 public class DTRequestInfo : IRequestInfo
{ /// <summary>
/// 构造函数
/// </summary>
/// <param name="key">键值</param>
/// <param name="body">接收的数据体</param>
public DTRequestInfo(string key,byte[] body)
{
this.Key = key;
this.Body = body;
}
public string Key
{
get;set;
}
/// <summary>
/// 请求信息缓存
/// </summary>
public byte[] Body { get; set; }
/// <summary>
/// 设备ID
/// </summary>
public string DeviceID { get; set; } }

RequestInfo

2、建立一个数据接收过滤器帮助 类,作为过滤器的继承的父类,主要的作用是用来接收处理客户端传类的二进制字符,返回有效的数据部分。

/// <summary>
/// 处理获取的所有数据
/// </summary>
/// <typeparam name="TRequestInfo"></typeparam>
public abstract class ReceiveFilterHelper<TRequestInfo> : ReceiveFilterBase<TRequestInfo>
where TRequestInfo : IRequestInfo
{
private SearchMarkState<byte> m_BeginSearchState;
private SearchMarkState<byte> m_EndSearchState;
private bool m_FoundBegin = false;
protected TRequestInfo NullRequestInfo = default(TRequestInfo);
/// <summary>
/// 初始化实例
/// </summary>
protected ReceiveFilterHelper()
{ }
/// <summary>
/// 过滤指定的会话
/// </summary>
/// <param name="readBuffer">数据缓存</param>
/// <param name="offset">数据起始位置</param>
/// <param name="length">缓存长度</param>
/// <param name="toBeCopied"></param>
/// <param name="rest"></param>
/// <returns></returns>
public override TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
{
rest = ;
int searchEndMarkOffset;
int searchEndMarkLength;
//在此处做了处理,将接收到的第一个字符作为起始过滤标志,到结束。返回指定长度的数据。
byte[] startMark = new byte[] { readBuffer[offset] };
byte[] endMark = new byte[] {0xff };
m_BeginSearchState = new SearchMarkState<byte>(startMark);
m_EndSearchState = new SearchMarkState<byte>(endMark);
//上一个开始标记长度
int prevMatched = ;
int totalParsed = ; if (!m_FoundBegin)
{
prevMatched = m_BeginSearchState.Matched;
int pos = readBuffer.SearchMark(offset, length, m_BeginSearchState, out totalParsed); if (pos < )
{
//不要缓存无效数据
if (prevMatched > || (m_BeginSearchState.Matched > && length != m_BeginSearchState.Matched))
{
State = FilterState.Error;
return NullRequestInfo;
} return NullRequestInfo;
}
else //找到匹配的开始标记
{
//But not at the beginning
if (pos != offset)
{
State = FilterState.Error;
return NullRequestInfo;
}
} //找到开始标记
m_FoundBegin = true; searchEndMarkOffset = pos + m_BeginSearchState.Mark.Length - prevMatched; //This block only contain (part of)begin mark
if (offset + length <= searchEndMarkOffset)
{
AddArraySegment(m_BeginSearchState.Mark, , m_BeginSearchState.Mark.Length, false);
return NullRequestInfo;
} searchEndMarkLength = offset + length - searchEndMarkOffset;
}
else//Already found begin mark
{
searchEndMarkOffset = offset;
searchEndMarkLength = length;
} while (true)
{
var prevEndMarkMatched = m_EndSearchState.Matched;
var parsedLen = ;
var endPos = readBuffer.SearchMark(searchEndMarkOffset, searchEndMarkLength, m_EndSearchState, out parsedLen); //没有找到结束标记
if (endPos < )
{
rest = ;
if (prevMatched > )//还缓存先前匹配的开始标记
AddArraySegment(m_BeginSearchState.Mark, , prevMatched, false);
AddArraySegment(readBuffer, offset, length, toBeCopied);
} //totalParsed += parsedLen;
//rest = length - totalParsed;
totalParsed = ;
byte[] commandData = new byte[BufferSegments.Count + prevMatched + totalParsed]; if (BufferSegments.Count > )
BufferSegments.CopyTo(commandData, , , BufferSegments.Count); if (prevMatched > )
Array.Copy(m_BeginSearchState.Mark, , commandData, BufferSegments.Count, prevMatched); Array.Copy(readBuffer, offset, commandData, BufferSegments.Count + prevMatched, totalParsed); var requestInfo = ProcessMatchedRequest(commandData, , commandData.Length); Reset();
return requestInfo;
if (prevMatched > )//Also cache the prev matched begin mark
AddArraySegment(m_BeginSearchState.Mark, , prevMatched, false);
AddArraySegment(readBuffer, offset, length, toBeCopied);
//return NullRequestInfo;
}
}
/// <summary>
/// Processes the matched request.
/// </summary>
/// <param name="readBuffer">The read buffer.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns></returns>
protected abstract TRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length); /// <summary>
/// Resets this instance.
/// </summary>
public override void Reset()
{
m_BeginSearchState.Matched = ;
m_EndSearchState.Matched = ;
m_FoundBegin = false;
base.Reset();
}
}

ReceiveFilterHelper

3、建立一个数据接收过滤器,继承ReceiveFilterHelper类,过来接收并过滤指定的信息内容。

 /// <summary>
/// 数据接收过滤器
/// </summary>
public class DTReceiveFilter : ReceiveFilterHelper<DTRequestInfo>
{
/// <summary>
/// 重写方法
/// </summary>
/// <param name="readBuffer">过滤之后的数据缓存</param>
/// <param name="offset">数据起始位置</param>
/// <param name="length">数据缓存长度</param>
/// <returns></returns>
protected override DTRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
{
//返回构造函数指定的数据格式
return new DTRequestInfo(Encoding.UTF8.GetString(readBuffer, offset, length), readBuffer);
}
}

ReceiveFilter

4、建立一个AppSession,用来发送和接收客户端信息,一个客户端相当于一个session,这一点很重要,应为每一个RUT设备都有一个固定的编号,需要给session的item中添加Key值用来区分不同的客户端。

 public class DTSession:AppSession<DTSession,DTRequestInfo>
{ protected override void HandleException(Exception e)
{
base.HandleException(e);
} protected override void OnSessionStarted()
{
base.OnSessionStarted();
}
protected override int GetMaxRequestLength()
{
return base.GetMaxRequestLength();
} protected override void HandleUnknownRequest(DTRequestInfo requestInfo)
{
base.HandleUnknownRequest(requestInfo);
}
}

AppSession

5、建立AppServer,自定义适合自己项目的服务。这个项目需要的就是服务端给客户端RTU发送数据请求指令,客户端才能做出响应返回数据十六进制的数据报文。

Timer requestTimer = null;

        public DTServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, DTRequestInfo>())
{
//定时发送请求压力的报文
double sendInterval = double.Parse(ConfigurationManager.AppSettings["sendInterval"]);
requestTimer = new Timer(sendInterval);
requestTimer.Elapsed += RequestTimer_Elapsed;
requestTimer.Enabled = true;
requestTimer.Start();
} private void RequestTimer_Elapsed(object sender, ElapsedEventArgs e)
{
//发送请求报文
var sessionList = GetAllSessions();
//Logger.Error(sessionList);
foreach (var session in sessionList)
{
Dictionary<string, string> routs = ConfigManager.GetAllConfig();
try
{
foreach (var item in routs)
{
if (item.Key.ToString().Contains("rout2_"))
{
string routeID = item.Key.ToString().Split('_')[];
byte[] rout = ConvertHelper.strToToHexByte(routeID);
byte[] address = ConvertHelper.strToToHexByte(item.Value.ToString());
/// 合成报文
List<byte> data = new List<byte>();
data.Add(rout[]);
data.Add(0x04);//读取数据
data.Add(address[]);
data.Add(address[]);
data.Add(address[]);
data.Add(address[]);
byte[] checkcode = CRC16.crc_16(data.ToArray());
data.Add(checkcode[]);
data.Add(checkcode[]);
/// 发送报文
//使用字节抽屉存储
// ArraySegment<byte> sendData = new ArraySegment<byte>(data.ToArray());
session.Send(data.ToArray(), , data.ToArray().Length);
// Console.WriteLine("发送数据:" + ConvertHelper.byteToHexStr(data.ToArray()));
}
}
}
catch (Exception ex)
{
//写入日志
/// Logger.Info(ex.Message);
}
}
}
protected override void OnNewSessionConnected(DTSession session)
{
base.OnNewSessionConnected(session);
//Logger.Error(session.SessionID);
}
protected override void ExecuteCommand(DTSession session, DTRequestInfo requestInfo)
{
base.ExecuteCommand(session, requestInfo);
}
protected override void OnStarted()
{
base.OnStarted();
}
protected override void OnStartup()
{
base.OnStartup();
} protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
return base.Setup(rootConfig, config);
}
}

AppServer

三、以上都为准备工作,下面具体的使用。

1、在程序启动时开启服务,用来监听客户端。

  appServer = new DTServer();
int Port = int.Parse(ConfigurationManager.AppSettings["Port"].ToString());
if (!appServer.Setup(Port))
{
label1.Text = "端口装载失败";
return;
}
if (!appServer.Start())
{
label1.Text = "服务启动失败";
return;
}
label1.Text = "压力采集终端服务已开启";
//监听地址
for (int i = ; i < IpEntry.AddressList.Length; i++)
{
if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
label2.Text = "监听IP:" + IpEntry.AddressList[i];
label3.Text = "端口:" + Port.ToString();
}
}
//注册事件
appServer.NewRequestReceived += new SuperSocket.SocketBase.RequestHandler<DTSession, DTRequestInfo>(appServer_NewRecivede);

2、请求接收事件

 private  void appServer_NewRecivede(DTSession session, DTRequestInfo requestInfo)
{
byte[] bytes = requestInfo.Body;
if (bytes[] == 0xfe)
return;
//设备地址
//设备地址
string devAddress = bytes[].ToString("X");
string dataType = bytes[].ToString("X");
if (dataType != "")
{
string tt = string.Empty;
foreach (var item in bytes.Take().Reverse())
{
tt += item.ToString("X2");
}
//resultInfo.DeviceID = tt;
session.Items["deviceid"] = tt;
return;
}
//数据长度
int dataLenth = bytes[];
//数据
byte[] datas = bytes.Skip().Take(dataLenth).ToArray();
//从缓冲区截取有效数据
string datastrs = ConvertHelper.byteToHexStr(bytes.Take(dataLenth + ).ToArray());
//采集值
int value = datas[] + datas[] * ;
//数值转换
//电流
double f = 3.3 / * value / * ;
//压力
double yl = 0.0625 * (f - );
if (session.Items.Count==)
{
return;
}
//将结果写入log
session.Logger.Info("设备编号:" + session.Items["deviceid"].ToString() + " 压力值:" + yl.ToString() + " 时间:" + DateTime.Now.ToString() + " 报文:" + datastrs);
}

3、利用SocketTool工具进行验证,发送十六进制字符,并在项目日志中进行查看结果。

 以上就是整个项目中的全部代码和开发过程。感兴趣的可加QQ:1301485237  交流学习。

SuperSocket基础二的更多相关文章

  1. SuperSocket基础(二)-----一个完成SocketServer项目

    SuperSocket基础(二)-----一个完成SocketServer项目 由于时间关系未能及时更新,关于SuperSocket,对于初学者而言,一个SuperSock的Server真的不好写.官 ...

  2. SuperSocket基础一

    SuperSocket基础(一)——————基本概念 项目中之前一直使用TCP socket服务框架,但是不利于扩展.最近刚接触到开源的superSocket感觉很不错,特记录一下.官方开源地址:ht ...

  3. Python全栈开发【基础二】

    Python全栈开发[基础二] 本节内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典) 其他(编码,range,f ...

  4. Bootstrap <基础二十九>面板(Panels)

    Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素添加 class .panel 和 class .pa ...

  5. Bootstrap <基础二十八>列表组

    列表组.列表组件用于以列表形式呈现复杂的和自定义的内容.创建一个基本的列表组的步骤如下: 向元素 <ul> 添加 class .list-group. 向 <li> 添加 cl ...

  6. Bootstrap<基础二十七> 多媒体对象(Media Object)

    Bootstrap 中的多媒体对象(Media Object).这些抽象的对象样式用于创建各种类型的组件(比如:博客评论),我们可以在组件中使用图文混排,图像可以左对齐或者右对齐.媒体对象可以用更少的 ...

  7. Bootstrap <基础二十六>进度条

    Bootstrap 进度条.在本教程中,你将看到如何使用 Bootstrap 创建加载.重定向或动作状态的进度条. Bootstrap 进度条使用 CSS3 过渡和动画来获得该效果.Internet ...

  8. Bootstrap <基础二十五>警告(Alerts)

    警告(Alerts)以及 Bootstrap 所提供的用于警告的 class.警告(Alerts)向用户提供了一种定义消息样式的方式.它们为典型的用户操作提供了上下文信息反馈. 您可以为警告框添加一个 ...

  9. Bootstrap<基础二十四> 缩略图

    Bootstrap 缩略图.大多数站点都需要在网格中布局图像.视频.文本等.Bootstrap 通过缩略图为此提供了一种简便的方式.使用 Bootstrap 创建缩略图的步骤如下: 在图像周围添加带有 ...

随机推荐

  1. CSS文字溢出处理问题

    单行省略 div { white-space:nowrap; //断行处理:无断行 text-overflow:ellipsis; //文字溢出处理:省略号 overflow:hidden; //溢出 ...

  2. Petrozavodsk Winter-2018. Jagiellonian U Contest

    A. XOR 求出所有数的异或和$sum$,将所有数and上$sum$,然后求线性基,则选取$sum$的所有$1$对应的基最优. 时间复杂度$O(n\log x)$. #include<cstd ...

  3. ECMA Script 6_数值的扩展

    1. 二进制 和 八进制 新的表示法 前缀 0b(或0B) 表示 二进制 前缀 0o(或 0O )表示 八进制 console.log(0b111110111 === 503); // true co ...

  4. 微信公众号_订阅号_access_token_创建菜单_菜单name+表情

    全局唯一接口调用凭据 access_token 用于接口调用的一个必要参数 有了 access_token 就能实现所有的接口 特点:  1. 有效期为 2 小时,所以 2 小时要更新一次,提前 5 ...

  5. window 日志清楚

    @echo offset srcDir="D:\ApplicationLogs"set daysAgo=15forfiles /p %srcDir% /s /m *.* /d -% ...

  6. laravel5.5 任务调度 (转载)

    Ubuntu和Mac环境中,我们可以使用crontab来控制计划任务 1. 定义调度 我们需要在 App\Console\Kernel 类的 schedule 方法中定义所有任务调度.例如: 上图中定 ...

  7. 修改MyEclipse字体大小及颜色

    windows-->prefereces->General-->Appearance-->Colors and Fonts,在右边找到要修改的字体或背景,双击点Edit修改即可 ...

  8. js_2_逻辑分支

  9. PDF文件怎么转换成PPT

    在日常办公中大家都会发现PDF文件目前是比较常见的一种文件,有的时候大家会需要将PDF转换成PPT为了去更好的演示,毕竟PPT文件在演示方面具有着较好的特点,那如何将PDF文件转换成PPT文件呢,今天 ...

  10. PHP Xdebug + PhpStorm调试远程服务器代码

    1.服务器(linux centos)安装xdebug pecl install xdebug 注意看安装完成之后会显示 debug.so 的路径,记录下来 2.配置 php.ini如果不知道php. ...