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. Execution failed for task':app;clean'

    Execution failed for task':app;clean' >Unable to delete directory:f:xxxxxbuild\output\apk当程序出先这个错 ...

  2. python基础(5):数字和字符串类型

    今天总结一下数据类型中的数字和字符串型. 预习: 小练习 一.数字(int,float) 在python3中数字类型只有整形,浮点型,复数.而复数在平时的编程中几乎用不到所以我们只要掌握整形和浮点型即 ...

  3. C3制作导航栏分割线及立体风格

    //首先写一个导航栏样式 .nav{    width:560px;    height: 50px;    font:bold 0/50px Arial;    text-align:center; ...

  4. 富文本编辑器UEditor自定义工具栏(一、基础配置与字体、背景色、行间距、超链接实现)

    导读:UEditor 是由百度「FEX前端研发团队」开发的所见即所得富文本web编辑器,功能强大,可定制,是一款优秀的国产在线富文本编辑器,编辑器内可插入图片.音频.视频等. 一.UEditor自定义 ...

  5. Angular页面加载后自动弹窗

    首先在控制器内写好一个弹窗,我用的是ionic的默认提示对话框 // 一个确认对话框 $scope.showConfirm = function() { var confirmPopup = $ion ...

  6. Android 性能测试——Memory Monitor 工具

    Android 性能测试--Memory Monitor 工具 Memory Monitor能做什么? 实时查看App的内存分配情况 快速判断App是否由于GC操作造成卡顿 快速判断App的Crash ...

  7. 用gensim学习word2vec

    在word2vec原理篇中,我们对word2vec的两种模型CBOW和Skip-Gram,以及两种解法Hierarchical Softmax和Negative Sampling做了总结.这里我们就从 ...

  8. JAVA环境变量关于

    1.为什么要设置classPath? 用于通知JVM Java基础类库的位置.classPath告诉类装载器去哪里寻找第三方类库 自JDK1.5之后便不需要再配置这个变量了 2.为什么安装两个JRE( ...

  9. Vmware Tools 下载及安装方法

    Vmware Tools 下载及安装方法 王尚2014.11.20 一.介绍 VMware Tools 是VMware 虚拟机中自带的一种增强工具,相当于 VirtualBox 中的增强功能(Sun ...

  10. 利用Apache commons-net 包进行FTP文件和文件夹的上传与下载

    首先声明:这段代码是我在网上胡乱找的,调试后可用. 需要提前导入jar包,我导入的是:commons-net-3.0.1,在网上可以下载到.以下为源码,其中文件夹的下载存在问题:FTPFile[] a ...