C# ModBus Tcp读写数据 与服务器进行通讯
前言
本文将使用一个NuGet公开的组件技术来实现一个ModBus TCP的客户端,方便的对Modbus tcp的服务器进行读写,这个服务器可以是电脑端C#设计的,也可以是PLC实现的,也可以是其他任何支持这个通信协议的服务器。
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
组件API地址:http://www.cnblogs.com/dathlin/p/7703805.html
关于两种模式
在PLC端,包括三菱和西门子,欧姆龙以及Modbus Tcp客户端的访问器上,都支持两种模式,短连接模式和长连接模式,现在就来解释下什么原理。
短连接:每次读写都是一个单独的请求,请求完毕也就关闭了,如果服务器的端口仅仅支持单连接,那么关闭后这个端口可以被其他连接复用,但是在频繁的网络请求下,容易发生异常,会有其他的请求不成功,尤其是多线程的情况下。
长连接:创建一个公用的连接通道,所有的读写请求都利用这个通道来完成,这样的话,读写性能更快速,即时多线程调用也不会影响,内部有同步机制。如果服务器的端口仅仅支持单连接,那么这个端口就被占用了,比如三菱的端口机制,西门子的Modbus tcp端口机制也是这样的。以下代码默认使用短连接,方便测试。
在短连接的模式下,每次请求都是单独的访问,所以没有重连的困扰,在长连接的模式下,如果本次请求失败了,在下次请求的时候,会自动重新连接服务器,直到请求成功为止。另外,尽量所有的读写都对结果的成功进行判断。
特别感谢
- 网友:陈恩富 对float,int数据的读取测试,才修复了权重位颠倒的BUG。
- 网友:U4幸福的蜗牛 发现了博客上错误的一个方法名称,已于2018年1月8日13:34:39更新。并反馈了一些特殊设备(modbus tcp服务器)的读取数据的BUG。已修复。
随便聊聊
只要是网络访问,就会存在主从的区别,此处的设计模式是客户端主动请求服务器数据,然后接收服务器的反馈数据,支持原生的指令收发,支持其他一些方便的API收发。特殊功能码需要使用原生收发的API,本组件支持如下的功能操作:
- 0x01 读取线圈的操作,
- 0x02 读取离散的操作,
- 0x03 读取寄存器的值,
- 0x05 写一个线圈操作,
- 0x06 写一个寄存器值,
- 0x0F 批量写线圈操作,
- 0x10 批量写寄存器值,
如果你的设备需要这些功能之外的数据,可以使用原生API方法,但是这个方法的前提就是你对MODBUS TCP协议非常清晰才可以,如果你不了解这个协议,可以参照下面的博客说明:
http://blog.csdn.net/thebestleo/article/details/52269999
如果你需要搭建自己的ModBus服务器,可以参照这边文章:http://www.cnblogs.com/dathlin/p/7782315.html
在你开发自己的客户端程序之前,可以先用MODBUS测试工具进行测试,以下地址的一个开源项目就是基于这个组件开发的Modbus tcp测试工具,可直接用于读写测试。
访问测试项目
下面的一个项目是这个组件的访问测试项目,您可以进行初步的访问的测试,免去了您写测试程序的麻烦,这个项目是和三菱,西门子PLC的访问写在一起的。可以同时参考。
下载地址为:HslCommunicationDemo.zip
Reference
ModBus组件所有的功能类都在 HslCommunication.ModBus命名空间,所以再使用之前先添加
using HslCommunication.ModBus;
using HslCommunication;
How to Use
实例化:
在使用读写功能之前必须先进行实例化:
private ModbusTcpNet busTcpClient = new ModbusTcpNet("192.168.1.195", 502, 0x01); // 站号1
上面的实例化指定了服务器的IP地址,端口号(一般都是502),以及自己的站号,允许设置为0-255,后面的两个参数有默认值,在实例化的时候可以省略。
private ModbusTcpNet busTcpClient = new ModbusTcpNet("192.168.1.195"); // 端口号502,站号1
注意:在Modbus服务器的设备里,大部分的设备都是从地址0开始的,有些特殊的设备是从地址1开始的,所以本组件里面,默认从地址0开始,如果想要从地址1开始,那么就需要如下的配置:
busTcpClient.AddressStartWithZero = False;
上面两个声明选择其中一个就行了。然后实例化之后(也可以放在窗体的Load方法中)就可以调用下面的方法切换为长连接了,
busTcpClient.ConnectServer();
关闭的话,调用如下的方法
busTcpClient.ConnectClose( );
以下代码演示常用的读写操作,为了方便起见,不再对IsSuccess判断,一般都是成功的:
private void userButton30_Click(object sender, EventArgs e)
{
// 读取操作
bool coil100 = busTcpClient.ReadCoil("100").Content; // 读取线圈100的通断
short short100 = busTcpClient.ReadInt16("100").Content; // 读取寄存器100的short值
ushort ushort100 = busTcpClient.ReadUInt16("100").Content; // 读取寄存器100的ushort值
int int100 = busTcpClient.ReadInt32("100").Content; // 读取寄存器100-101的int值
uint uint100 = busTcpClient.ReadUInt32("100").Content; // 读取寄存器100-101的uint值
float float100 = busTcpClient.ReadFloat("100").Content; // 读取寄存器100-101的float值
long long100 = busTcpClient.ReadInt64("100").Content; // 读取寄存器100-103的long值
ulong ulong100 = busTcpClient.ReadUInt64("100").Content; // 读取寄存器100-103的ulong值
double double100 = busTcpClient.ReadDouble("100").Content; // 读取寄存器100-103的double值
string str100 = busTcpClient.ReadString("100", 5).Content;// 读取100到104共10个字符的字符串 // 写入操作
busTcpClient.WriteCoil("100", true);// 写入线圈100为通
busTcpClient.Write("100", (short)12345);// 写入寄存器100为12345
busTcpClient.Write("100", (ushort)45678);// 写入寄存器100为45678
busTcpClient.Write("100", 123456789);// 写入寄存器100-101为123456789
busTcpClient.Write("100", (uint)123456778);// 写入寄存器100-101为123456778
busTcpClient.Write("100", 123.456);// 写入寄存器100-101为123.456
busTcpClient.Write("100", 12312312312414L);//写入寄存器100-103为一个大数据
busTcpClient.Write("100", 12634534534543656UL);// 写入寄存器100-103为一个大数据
busTcpClient.Write("100", 123.456d);// 写入寄存器100-103为一个双精度的数据
busTcpClient.Write("100", "K123456789"); }
下面再分别讲解严格的操作,以及批量化的复杂的读写操作,假设你要读取1000个M,循环读取1千次可能要3秒钟,如果用了下面的批量化读取,只需要50ms,但是需要你对字节的原理比较熟悉才能得心应手的处理
读取线圈API:
在此处举例读取地址为0,长度为10的线圈数量,读取出来的数据已经自动转化成了bool数组,方便的进行二次处理:
private void userButton8_Click(object sender,EventArgs e)
{
HslCommunication.OperateResult<bool[]> read = busTcpClient.ReadCoil("0", 10);
if(read.IsSuccess)
{
bool coil_0 = read.Content[0];
bool coil_1 = read.Content[1];
bool coil_2 = read.Content[2];
bool coil_3 = read.Content[3];
bool coil_4 = read.Content[4];
bool coil_5 = read.Content[5];
bool coil_6 = read.Content[6];
bool coil_7 = read.Content[7];
bool coil_8 = read.Content[8];
bool coil_9 = read.Content[9];
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
当然也可以用组件提供的数据转换API实现数据提取:
读取离散数据:
读取离散数据和读取线圈的代码几乎是一致的,处理方式也是一致的,只是方法名称改成了:
private void userButton8_Click(object sender,EventArgs e)
{
HslCommunication.OperateResult<bool[]> read = busTcpClient.ReadDiscrete("0", 10);
if(read.IsSuccess)
{
bool coil_0 = read.Content[0];
bool coil_1 = read.Content[1];
bool coil_2 = read.Content[2];
bool coil_3 = read.Content[3];
bool coil_4 = read.Content[4];
bool coil_5 = read.Content[5];
bool coil_6 = read.Content[6];
bool coil_7 = read.Content[7];
bool coil_8 = read.Content[8];
bool coil_9 = read.Content[9];
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
读取寄存器数据:
假设我们需要读取地址为0,长度为10的数据,也即是10个数据,每个数据2个字节,总计20个字节的数据。下面解析数据前,先进行了假设,你在解析自己的数据前可以参照下面的解析
private void userButton10_Click(object sender, EventArgs e)
{
HslCommunication.OperateResult<byte[]> read = busTcpClient.Read("0", 10);
if (read.IsSuccess)
{
// 共返回20个字节,每个数据2个字节,高位在前,低位在后
// 在数据解析前需要知道里面到底存了什么类型的数据,所以需要进行一些假设:
// 前两个字节是short数据类型
short value1 = busTcpClient.ByteTransform.TransInt16(read.Content, 0);
// 接下来的2个字节是ushort类型
ushort value2 = busTcpClient.ByteTransform.TransUInt16(read.Content, 2);
// 接下来的4个字节是int类型
int value3 = busTcpClient.ByteTransform.TransInt32(read.Content, 4);
// 接下来的4个字节是float类型
float value4 = busTcpClient.ByteTransform.TransFloat(read.Content, 8);
// 接下来的全部字节,共8个字节是规格信息
string speci = Encoding.ASCII.GetString(read.Content, 12, 8); // 已经提取完所有的数据
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
写一个线圈:
写一个线圈,这个相对比较简单,假设我们需要写入线圈0,为通
private void userButton11_Click(object sender, EventArgs e)
{
HslCommunication.OperateResult write = busTcpClient.WriteCoil("0", true);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
写一个寄存器:
写一个寄存器的操作也是非常的方便,在这里提供了三个重载的方法,允许使用三种方式写入:分别写入,short,ushort,byte三种:
private void userButton12_Click(object sender, EventArgs e)
{
short value = -1234;
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
private void userButton12_Click(object sender, EventArgs e)
{
ushort value = 56713;
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
private void userButton12_Click(object sender, EventArgs e)
{
// 0x00为高位,0x10为低位
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister("0", 0x00, 0x10);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
批量写入线圈:
private void userButton13_Click(object sender, EventArgs e)
{
// 线圈0为True,线圈1为false,线圈2为true.....等等,以此类推,数组长度多少,就写入多少线圈
bool[] value = new bool[] { true, false, true, true, false, false };
HslCommunication.OperateResult write = busTcpClient.WriteCoil("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
批量写入寄存器:
第一种情况写入一串short数组,这种情况比较简单:
private void userButton14_Click(object sender, EventArgs e)
{
short[] value = new short[] { -1234, 467, 12345 };
HslCommunication.OperateResult write = busTcpClient.Write("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
第二情况写入一串ushort数组,也是比较简单:
private void userButton14_Click(object sender, EventArgs e)
{
ushort[] value = new ushort[] { 46789, 467, 12345 };
HslCommunication.OperateResult write = busTcpClient.Write("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
比较复杂的是写入自定义的数据,按照上述读取寄存器,比如我需要写入寄存器0,寄存器1共同组成的一个int数据,那么我们这么写:
private void userButton15_Click(object sender, EventArgs e)
{
int value = 12345678;// 等待写入的一个数据 HslCommunication.OperateResult write = busTcpClient.Write("0", value);
if (write.IsSuccess)
{
// 写入成功
textBox1.Text = "写入成功";
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
其他数据参考这个就行,如果有不明白的,可以联系上面的QQ群。
模式切换(支持热切换,想什么时候切换都可以):
上面默认都是使用短连接的机制,如果需要使用长连接的话,这种通讯模式更加稳定。多线程已经同步。
private void userButton11_Click(object sender, EventArgs e)
{
modBusTcpClient.ConnectServer();
}
执行完这一行代码后,一般在实例化后面就可以切换长连接了,会返回一个OperateResult对象,连接成功IsSuccess为True,后面所有的读写操作都调用同一个通信通道。如果想要切换回短连接。
modBusTcpClient.ConnectClose();
究极数据操作,使用原生的报文来操作数据:
传入一个字节数组,数据内容和原生的数据一致,比如我要通过原生API读取寄存器地址为0,长度为3的数据,那么字节的HEX标识形式为 00 00 00 00 00 06 00 03 00 00 00 03
private void userButton2_Click(object sender, EventArgs e)
{
byte[] data = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes("00 00 00 00 00 06 00 03 00 00 00 03"); HslCommunication.OperateResult<byte[]> read = busTcpClient.ReadFromCoreServer(data);
if(read.IsSuccess)
{
// 获取结果,并转化为Hex字符串,方便显示
string result = HslCommunication.BasicFramework.SoftBasic.ByteToHexString(read.Content, ' ');
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
上述代码在操作时用了一个转化机制,输入为十六进制的文本,转化为byte[]数据,中间的分割符可以为空格,可以为'-',也可以为',','_'等等等等,调用了组件基础的数据转化功能。
C# ModBus Tcp读写数据 与服务器进行通讯的更多相关文章
- 1、如何抓取Modbus TCP/UDP 数据包实战
CEIWEI最近发布了Modbus RTU Over TCP/UDP 过滤监控的新工具,下面以Modbus RTU TCP为示例,讲解如何抓取Modbus通信数据包,因为CEIWEI ModbusMo ...
- 基于Qt实现的TCP端口数据转发服务器
对于Qt,比较喜欢qt的sdk框架,我也是用于做一些工作中用到的工具软件,基于qt的sdk做起来也比较快: 一.概述 今天要说的这个tcp端口转发服务器,主要是用于将监听端口的数据转发到另外一个服务器 ...
- modbus tcp数据报文结构
modbus tcp数据报文结构 请求:00 00 00 00 00 06 09 03 00 00 00 01 响应:00 00 00 00 00 05 09 03 02 12 34 一次modbus ...
- GPRS以TCP上传数据到服务器OK,但收不到服务器下发的数据
GPRS以TCP上传数据到服务器OK,但收不到服务器下发的数据 基站漂移是DTU很常见的连接故障,一个DTU所处的地方可能会有多个基站信号,时间久了,可能会在不同的基站之间切换,它会更新自己的连接,发 ...
- QT 简单 TCP 通信,发送数据到服务器
1.首先 添加头文件 #include <QtNetwork/QTcpSocket> 并且 在 xxx.pro(xxx指工程的名称) 中QT += core gui下面,添加 下面两句句话 ...
- 基于STM32和W5500的Modbus TCP通讯
在最近的一个项目中需要实现Modbus TCP通讯,而选用的硬件平台则是STM32F103和W5500,软件平台则选用IAR EWAR6.4来实现. 1.移植千的准备工作 为了实现Modbus TCP ...
- 开放型Modbus/TCP 规范
修订版 1.0,1999 年3 月29 日Andy SwalesSchneider 电气公司aswales@modicon.com目录目录............................... ...
- LwIP之socket应用--WebServer和Modbus TCP
1. 引言 LwIP是嵌入式领域一个流行的以太网协议栈, LwIP开放源码,用C写成非常方便移植,并且支持socket接口,使用者可以集中精力处理应用功能. 本文就是LwIP socket使用的一个小 ...
- C# 读写欧姆龙(Omron)PLC ,C#使用Fins-tcp协议读写数据
本文将使用一个gitHub开源的组件技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作 ...
随机推荐
- UI自动化(三)css优先级
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- 通过WireShark抓取iPhone联网数据方法
通过WireShark抓取iPhone联网数据方法 命令行 rvictl -s <UDID> 然后再wireshark选择rvi0进行抓包即可 抓包完后,移除用命令 rvictl -x & ...
- Qt信号和槽机制
概述 信号和槽机制是QT的核心机制,要精通QT编程就必须对信号和槽有所了解.信号和槽是一种高级接口,应用于对象之间的通信,他是QT的核心特性,也是QT差别于其他工具包的重要地方.信号和槽是QT自行定义 ...
- [从零开始搭网站八]CentOS使用yum安装Redis的方法
1.由于centOS官方yum源里面没有Redis,这里我们需要安装一个第三方的yum源,这里用了fedora的epel仓库 yum install epel-release 安装过程中会有让你确认的 ...
- springboot2.0 最大上传文件大小遇到的错误Failed to bind properties under 'spring.servlet.multipart.max-file-size'
错误: 解决: 把100Mb改为100MB
- python tar 打包
import os import tarfile def make_targz_one_by_one(output_filename, source_dir): tar = tarfile.open( ...
- 短路运算符(逻辑与&& 和 逻辑或||)
首先我们来解释一下短路运算符: 短路运算符就是从左到右的运算中前者满足要求,就不再执行后者了: 可以理解为: &&为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执 ...
- 新建node工程
之前各种node工程都是东抄抄,西抄抄的. 用ng的cli之后,发现非常舒服.所以把node新建工程的种种记录一下. node+babel, 直接按es6标准写就好了, 不需要一定写ts再转码了( ...
- JxBrowser之二:常用函数addLoadListener
1.常用函数addLoadListener,包含对页面加载状态的多种监控回调. browser.addLoadListener(new LoadAdapter() { @Override public ...
- guxh的python笔记十:包和模块
1,包和模块 包package:本质就是一个文件夹/目录,必须带一个__init.__.py的文件 模块module:.py结尾的python文件 2,导入方法 import pandas, coll ...