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基础(二)-----一个完成SocketServer项目的更多相关文章

  1. SuperSocket基础二

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

  2. (转)VS2015基础 指定一个或多个项目执行 - 心少朴的博客

           慈心积善融学习,技术愿为有情学.善心速造多好事,前人栽树后乘凉.我今于此写经验,愿见文者得启发. 这个解决方案下,有两个项目, 看到黑体的project了吗?它就是指定执行的项目. 这两 ...

  3. Django(一)基础:安装环境、创建项目、视图、创建一个项目的应用(app)

    一.安装环境 参考: https://docs.djangoproject.com/zh-hans https://www.runoob.com/django/django-install.html ...

  4. 如何搭建一个WEB服务器项目(二)—— 对数据库表进行基本的增删改查操作

    使用HibernateTemplate进行增删改查操作 观前提示:本系列文章有关服务器以及后端程序这些概念,我写的全是自己的理解,并不一定正确,希望不要误人子弟.欢迎各位大佬来评论区提出问题或者是指出 ...

  5. SuperSocket基础一

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

  6. 【SSM之旅】Spring+SpringMVC+MyBatis+Bootstrap整合基础篇(一)项目简介及技术选型相关介绍

    试水 一直想去搭建个自己的个人博客,苦于自己的技术有限,然后也个人也比较懒散.想动而不能动,想动而懒得动,就这么一直拖到了现在.总觉得应该把这几年来的所学总结一番,这样才能有所成长. 不知在何时,那就 ...

  7. SpringBoot入门教程(二)CentOS部署SpringBoot项目从0到1

    在之前的博文<详解intellij idea搭建SpringBoot>介绍了idea搭建SpringBoot的详细过程, 并在<CentOS安装Tomcat>中介绍了Tomca ...

  8. 栈长这里是生成了一个 Maven 示例项目。

    Spring Cloud 的注册中心可以由 Eureka.Consul.Zookeeper.ETCD 等来实现,这里推荐使用 Spring Cloud Eureka 来实现注册中心,它基于 Netfl ...

  9. 【vue】创建一个vue前端项目,编译,发布

    npm: Nodejs下的包管理器. webpack: 它主要的用途是通过CommonJS的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包. vue-cli: 用户生成Vue工 ...

随机推荐

  1. CJOJ 2307 【一本通】完全背包(动态规划)

    CJOJ 2307 [一本通]完全背包(动态规划) Description 设有n种物品,每种物品有一个重量及一个价值.但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干 ...

  2. POJ 3126 math(BFS)

    Prime Path Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 21581   Accepted: 11986 Desc ...

  3. hdu_1695: GCD 【莫比乌斯反演】

    题目链接 这题求[1,n],[1,m]gcd为k的对数.而且没有顺序. 设F(n)为公约数为n的组数个数 f(n)为最大公约数为n的组数个数 然后在纸上手动验一下F(n)和f(n)的关系,直接套公式就 ...

  4. 教你做炫酷的碎片式图片切换 (canvas)

    前言 老规矩,先上 DEMO 和 源码.图片区域是可以点击的,动画会从点击的位置开始发生. 本来这个效果是我3年前做的,只是当是是用无数个 div 标签完成的,性能比较成问题,在移动端完全跑不动.最近 ...

  5. 【RequireJS】requireJS的基础知识

    1. requirejs的优点 1)异步加载依赖的文件 2)管理文件加载顺序 3)管理文件加载的包路径 2. requirejs下载地点 https://github.com/jrburke/requ ...

  6. JAVA学习路线图(一文详解)

    此乃是java攻城狮的学习路线图,由简到繁,由易到难,一步步的学习,最后成为JAVA攻城狮. 阶段1 1:学习HTML 2:学习CSS 3:JavaScript 4:jQuery 5:xml解析 6: ...

  7. centos 7 下面安装oracle 11g r2 过程分享

    本人对LINUX等很多还不熟悉,如果有不对的地方还请各位指正.谢谢. 打算学习下ORACLE,RMAN备份与还原功能,所以安装了虚拟机,用的是centos7 X86_64-1611版本,oracle用 ...

  8. HDU 6069

    Counting Divisors Problem Description In mathematics, the function d(n) denotes the number of diviso ...

  9. year:2017 month:8 day:1+

    2017-08-01 JAVAse 方法的重载:在同一个类中存在一个以上的同名方法,只要他们的参数数量,参数类型,参数顺序(两个相同类型的参数是不行的)这样就构成了方法的重载. 有返回值的方法有三种调 ...

  10. PHP appserv + ZendStudio12.5.1 + 注册码

    PHP软件安装  百度云盘 安装激活破解ZendStudio12.05(注册码)     Zend Studio 配置 apache server