C# 开发ModBus的服务器程序 实现ModBus数据总站 搭建自定义的Modbus服务器 同时支持tcp和rtu
前言
本文将使用一个NuGet公开的组件技术来实现一个ModBus TCP的服务器端数据引擎,方便的实现接收来自各种设备的数据。并且该服务器模拟真实的设备,包含了数据池功能,可以接受来自任何支持Modbus tcp的客户端进行读写数据。C#实现的客户端类请参考下面这篇文章:http://www.cnblogs.com/dathlin/p/7885368.html 可以进行一些客户端服务器的联合调试。
nuget地址:https://www.nuget.org/packages/HslCommunication/
github地址:https://github.com/dathlin/HslCommunication 如果喜欢可以star或是fork,还可以打赏支持。
联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation
在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:
Install-Package HslCommunication
NuGet安装教程 http://www.cnblogs.com/dathlin/p/7705014.html
下载地址
此处提供一个服务器的Demo软件,下载解压就可以直接运行,这个Demo的源代码也在上面的示例,界面如下:后续新的版本可能会有点小区别
https://github.com/dathlin/HslCommunication/raw/master/Download/ModbusTcpServer.zip 这个地址的服务器软件永远都是最新的,会不停的更新。
如果您需要一个测试的客户端,包括了tcp和rtu的都可以,下载下面的Demo即可,支持各种配置信息
随便聊聊
使用本组件可以快速搭建一个高性能的MODBUS TCP总站,当我们一个上位机需要读取100台西门子PLC设备(此处只是举个例子,凡是都是使用Modbus tcp的都是一样的)的时候,你采用服务器主动去请求100台设备的机制对性能来说是个极大的考验,如果开100个线程去轮询100台设备,那么性能损失将是非常大的,更不用说再增加设备,如果搭建Modbus tcp服务器,就可以完美的解决性能问题,因为连接的压力将会平均分摊给每一台PLC,服务器端只要新增一个时间戳就可以知道客户端有没有连接上。
我们在100台PLC里都增加发送Modbus tcp方法,将数据发送到服务器的ip和端口上去,服务器根据站号来区分设备。这样就可以搭建一个高性能总站。
关于数据池
本服务器端拥有两个数据池,线圈数据池和寄存器数据池,任何客户端包括服务器本身都可以对数据池进行读写数据,比如PLC1将所有的数据发送到寄存器地址0-99上,PLC2将数据发送到100-199上。
- 线圈数据池 模拟了真实的数据读写,自动解析指令并返回数据
- 离散输入数据池 服务器端允许读写,对客户端来说仅仅支持读,功能码02
- 寄存器数据池 模拟了真实的数据读写
- 输入寄存器数据池 服务器端允许读写,对客户端来说仅仅支持读,功能码04
四个数据池的地址范围都是0-65535,起始地址是从0开始
Reference
ModBus组件所有的功能类都在 HslCommunication.ModBus命名空间,所以再使用之前先添加
using HslCommunication.ModBus;
How to Use
如果想快速的搭建一个Modbus-Tcp的服务器,只要2行代码即可,实例化,启动,而下面的例子稍微复杂了一点,额外配置了日志记录器,绑定了一个数据接收的方法,每当客户端进行数据交互,就会触发,可用于实现自定义功能的Modbus-Tcp服务器
private HslCommunication.ModBus.ModbusTcpServer busTcpServer; private void button1_Click( object sender, EventArgs e )
{
if(!int.TryParse(textBox2.Text,out int port))
{
MessageBox.Show( "端口输入不正确!" );
return;
} try
{ busTcpServer = new HslCommunication.ModBus.ModbusTcpServer( );
busTcpServer.LogNet = new HslCommunication.LogNet.LogNetSingle( "logs.txt" );
busTcpServer.LogNet.BeforeSaveToFile += LogNet_BeforeSaveToFile;
busTcpServer.OnDataReceived += BusTcpServer_OnDataReceived;
busTcpServer.ServerStart( port ); button1.Enabled = false;
panel2.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show( ex.Message );
}
} private void BusTcpServer_OnDataReceived( byte[] modbus )
{
if (InvokeRequired)
{
BeginInvoke( new Action<byte[]>( BusTcpServer_OnDataReceived ), modbus );
return;
}
textBox1.AppendText( "接收数据:" + HslCommunication.BasicFramework.SoftBasic.ByteToHexString(modbus) + Environment.NewLine );
} /// <summary>
/// 当有日志记录的时候,触发,将日志信息也在主界面进行输出
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LogNet_BeforeSaveToFile( object sender, HslCommunication.LogNet.HslEventArgs e )
{
if(InvokeRequired)
{
BeginInvoke( new Action<object, HslCommunication.LogNet.HslEventArgs>( LogNet_BeforeSaveToFile ), sender, e );
return;
} textBox1.AppendText( e.HslMessage.ToString( ) + Environment.NewLine );
}
RTU支持
当客户端进行连接了一个串口线后,也可以方便的让服务器的支持同时支持串口访问,
busTcpServer.StartSerialPort( "Com3" );
默认的串口参数是9600波特率。8位数据位,无奇偶校验,1位停止位,当然也可以自行指定波特率
busTcpServer.StartSerialPort( "Com3",9600 );
还可以传入一个初始化的委托方法,可以实现任意的支持。
创建数据订阅
当客户端进行发送读写指定时,如果你使用了OnDataReceived事件,都会触发,当然可能这并不是你需要的,如果我们相对一个地址上的数据进行监视,也就当有客户端写入的时候触发,或者是当这个数据更改了的时候触发,那么我们可以创建自己的数据订阅器:
private void button2_Click( object sender, EventArgs e )
{
// 点击数据监视
ModBusMonitorAddress monitorAddress = new ModBusMonitorAddress( );
monitorAddress.Address = ushort.Parse( textBox6.Text );
monitorAddress.OnChange += MonitorAddress_OnChange;
monitorAddress.OnWrite += MonitorAddress_OnWrite;
busTcpServer.AddSubcription( monitorAddress );
button2.Enabled = false;
} private void MonitorAddress_OnWrite( ModBusMonitorAddress monitor, short value )
{
// 当有客户端写入时就触发
} private void MonitorAddress_OnChange( ModBusMonitorAddress monitor, short befor, short after )
{
// 当该地址的值更改的时候触发
if(InvokeRequired)
{
BeginInvoke( new Action<ModBusMonitorAddress, short, short>( MonitorAddress_OnChange ), monitor, befor, after );
return;
} textBox9.Text = after.ToString( ); label11.Text = "写入时间:" + DateTime.Now.ToString( ) + " 修改前:" + befor + " 修改后:" + after;
}
当然,你可以只使用其中的一种数据订阅,比如上述的操作,一旦有客户端写入地址0x01的数据(无论是写入一个寄存器还是批量),那么马上会触发如下的方法。注意,从服务器自身写入的数据不会触发,所有的订阅都可以关联同一个方法,根据地址的不同来区分。
特别说明:
服务器只负责接受Modbus TCP协议的数据,无论客户端发了读写指令,都会触发接收事件。在服务器端有一个数据池,存储了线圈数据和寄存器数据,模拟了一个真实的设备,允许客户端使用Modbus tcp协议对服务器进行数据读写,并返回真实的数据,如果设备往这个服务器的寄存器地址写了100,那么服务器端或者其他客户端去读取寄存器100地址的值的时候,那么也是100.
下面演示了一些简单的数据读写,用于服务器端进行操作的。
bool Coil100 = busTcpServer.ReadCoil( "100" ); // 读线圈100的值
bool[] Coil100_109 = busTcpServer.ReadCoil( "100", 10 ); // 读线圈数组
short Short100 = busTcpServer.ReadInt16( "100" ); // 读取寄存器值
ushort UShort100 = busTcpServer.ReadUInt16( "100" ); // 读取寄存器ushort值
int Int100 = busTcpServer.ReadInt32( "100" ); // 读取寄存器int值
uint UInt100 = busTcpServer.ReadUInt32( "100" ); // 读取寄存器uint值
float Float100 = busTcpServer.ReadFloat( "100" ); // 读取寄存器Float值
long Long100 = busTcpServer.ReadInt64( "100" ); // 读取寄存器long值
ulong ULong100 = busTcpServer.ReadUInt64( "100" ); // 读取寄存器ulong值
double Double100 = busTcpServer.ReadDouble( "100" ); // 读取寄存器double值 busTcpServer.WriteCoil( "100", true ); // 写线圈的通断
busTcpServer.Write( "100", (short)5 ); // 写入short值
busTcpServer.Write( "100", (ushort)45678 ); // 写入ushort值
busTcpServer.Write( "100", 12345667 ); // 写入int值
busTcpServer.Write( "100", (uint)12312312 );// 写入uint值
busTcpServer.Write( "100", 123.456f ); // 写入float值
busTcpServer.Write( "100", 1231231231233L );// 写入long值
busTcpServer.Write( "100", 1212312313UL ); // 写入ulong值
busTcpServer.Write( "100", 123.456d ); // 写入double值
读写离散量:
bool value_100 = busTcpServer.ReadDiscrete( "100" ); // 读取地址100的离散量
bool[] value_100_109 = busTcpServer.ReadDiscrete( "100", 10 ); // 读取数据 busTcpServer.WriteDiscrete( "100", true); // 地址100为true
busTcpServer.WriteDiscrete( "100", new bool[]{true,true}); // 地址100-101为true
读写输入寄存器:
输入寄存器的读写方式和寄存器的是一致的,只是地址改一下就好了,也即使用富地址的方式,举个例子,写输入寄存器地址100为123
busTcpServer.Write( "x=4;100", (short)123 );
之前写100,现在改成"x=4;123" x=4也即是使用功能码04,那么之前寄存器的地址100等效于"x=3;100"
其他格式的数据参照这个即可。
过滤客户端:
支持对客户端的IP地址过滤,禁止不信任的Ip登录
modbusTcpServer.SetTrustedIpAddress( new List<string>( "192.168.0.100", "192.168.0.101") );
如果要移除限制,重新恢复所有的客户端登录,那么按照如下操作
modbusTcpServer.SetTrustedIpAddress( null );
主动连接客户端:
本服务器支持主动连接客户端,然后再进行数据交互,这种情况在有些工业上应用还是很广泛的
OperateResult connect = busTcpServer.ConnectHslAlientClient( "192.168.0.111", "10000", "12345678901" );
if (connect.IsSuccess)
{
MessageBox.Show( "连接成功!" );
}
else
{
MessageBox.Show( "连接失败!原因:" + connect.Message );
}
}
需要制定对方的IP地址,端口号,以及唯一识别码,目前使用的注册包协议为HslAlien协议,过几天专门写一篇这个协议的博文,如果需要连接其他定制的客户端,请联系作者进行定制。
注意:
数据串的第7个字节为Modbus的站号信息,如下界面是FF,也即255,可以以此来区分不同的设备发来的数据信息,所以此处一个服务器实例挂的最大客户端数为256台设备,前两个字节为消息的头序列,如果设备可以固定消息头,用这个来标识设备的话,就可以区分65536台设备。
创作不易,感谢打赏:
参考链接:
如果你对MODBUS TCP不熟悉,那么请参照如下地址,我就是参照该地址的博客开发的代码:
http://blog.csdn.net/thebestleo/article/details/52269999
C# 开发ModBus的服务器程序 实现ModBus数据总站 搭建自定义的Modbus服务器 同时支持tcp和rtu的更多相关文章
- 服务器编程入门(5)Linux服务器程序规范
问题聚焦: 除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范. 工欲善其事,必先利其器,这篇主要来探 ...
- Linux 高性能服务器编程——Linux服务器程序规范
问题聚焦: 除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范. 工欲善其事,必先利其器,这篇主要来探 ...
- 性能追击:万字长文30+图揭秘8大主流服务器程序线程模型 | Node.js,Apache,Nginx,Netty,Redis,Tomcat,MySQL,Zuul
本文为<高性能网络编程游记>的第六篇"性能追击:万字长文30+图揭秘8大主流服务器程序线程模型". 最近拍的照片比较少,不知道配什么图好,于是自己画了一个,凑合着用,让 ...
- 最简单的回射客户/服务器程序、time_wait 状态
下面通过最简单的客户端/服务器程序的实例来学习socket API. echoser.c 程序的功能是从客户端读取字符然后直接回射回去. C++ Code 1 2 3 4 5 6 7 8 9 10 ...
- 手动搭建自己的nuget服务器及使用
这篇文章的主要目的: 1.搭建自己的私有的nuget服务器 2.打包代码为nuget包 3.在其他项目中使用私有服务器上的nuget包 一. 搭建自己的nuget服务器 1. 创建一个空的ASP.NE ...
- 如何用Baas快速在腾讯云上开发小程序-系列1:搭建API & WEB WebSocket 服务器
版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/221059001487422606 来源:腾云阁 h ...
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
一.前言 大家都知道,在之前,我们Asp.net 的网站都只能部署在IIS上,并且IIS也只存在于Windows上,这样Asp.net开发的网站就难以做到跨平台.由于微软的各项技术的开源,所以微软自然 ...
- 使用VS Code 开发.NET Core 应用程序 部署到Linux 跨平台
使用VS Code 开发.NET Core 应用程序 部署到Linux 跨平台. 前面讲解了VSCode开发调试 .NET Core.都只是在windows下运行. .NET Core真正的核心是跨平 ...
- 开发系统时候运行程序突然报出“WebDev.WebServer40.exe已停止工作”的错误
已经解决,问题描述:在开发系统时候运行程序突然报出“WebDev.WebServer40.exe已停止工作”的错误,程序调试运行,发现程序在打开数据库时候报错,也就是Connection.Open() ...
随机推荐
- BIOS备忘录之ASL code常用知识点
_HID:device唯一 _STA:决定device在不在(在DM下面能不能看到) _CRS:描述分配给device的资源 _INI:在OSPM加载描述表的时候运行一次(比如,如果要根据不同情况给d ...
- Docker Kubernetes 创建管理 Deployment
Docker Kubernetes YAML文件创建容器 通过创建Deployment来管理pods从而创建容器.它会同时创建容器.pod.以及Deployment ! 环境: 系统:Centos 7 ...
- Linux 查看系统负载
查看系统负 # 查看系统负载 命令:uptime :: up :, users, load average: 0.00, 0.00, 0.00 注:load average: 0.00, 0.00, ...
- rds下载备份集在另外一台机器上恢复并应用binlog日志
-----------------------------备份还原并启动数据库----------------------------------1.创建目录,并把下载的压缩文件拷贝到相应的目录[ro ...
- Apache web服务器(LAMP架构)
Apache web服务器(LAMP架构) apache介绍 1).世界上使用率最高的网站服务器,最高时可达70%:官方网站:apache.org 2).http 超文本协议 HTML 超文本标记语言 ...
- gevent-协程用法
文章介绍了一种采用循环的方式生产协程列表,并可以向协程函数传递参数... # 协程引用import gevent from gevent import monkey, pool monkey.patc ...
- css及HTML知识点
html : 180° 输出为 css: margin: 0 auto;会在页面水平居中显示 box-shadow: 0 0 5px #f61818; 设置投影的位置大小颜色 outline ...
- 『TensorFlow』读书笔记_TFRecord学习
一.程序介绍 1.包导入 # Author : Hellcat # Time : 17-12-29 import os import numpy as np np.set_printoptions(t ...
- 『TensorFlow』SSD源码学习_其七:损失函数
Fork版本项目地址:SSD 一.损失函数介绍 SSD损失函数分为两个部分:对应搜索框的位置loss(loc)和类别置信度loss(conf).(搜索框指网络生成的网格) 详细的说明如下: i指代搜索 ...
- xlwt 写sheet xls 文件
import xlwtworkbook = xlwt.Workbook()#创建bookworksheet = workbook.add_sheet('My Sheet') #添加sheet#背景色的 ...