前言
- 【作者】:编程笔记in
- 【原文】:mp.weixin.qq.com/s/EerOuTF2HK72ykNHJS1duQ
- 本文描述如何使用C#原生的Socket类实现ModbusTCP从站功能。
- ModbusTCP从站是作为响应设备(服务器端)被动接收并处理主站(客户端)的请求,通过使用Socket创建从站服务,用于侦听主站(客户端)连接,获取请求及数据响应。
项目介绍
- 项目实现了线圈(Coils)和保持寄存器(Holding Registers)的数据临时缓存功能,其中线圈是2进制数据,寄存器是16位整形数据。所以可以创建bool数组(boo[])和ushort数组(ushort[])存储数据。
- 使用Socket接收发送,在接收数据后,按照Modbus的协议格式转换数据,再将转换后的数据发送回去,达到通讯效果。
- 下面是实现的数据处理功能。
1、处理客户端功能码:
- ① 处理线圈读取(功能码0x01)。
- ② 处理保持寄存器读取(功能码0x03)。
- ③ 处理线圈写入(功能码0x05)。
- ④ 处理寄存器写入(功能码0x06)。
- ⑤ 处理多线圈写入(功能码0x0F)。
- ⑥ 处理多寄存器写入(功能码0x10)。
2、实现功能:
-
创建设置和获取线圈和寄存器数据的方法,基本功能如(①②③④),方法是直接设置或获取数组的值,没有使用Modbus协议。属于测试功能,仅限测试时使用(正常情况不建议直接修改值)。
-
① 获取单个或多个线圈的值。
-
② 获取单个或多个寄存器的值。
-
③ 设置单个或多个线圈。
-
④ 设置单个或多个寄存器。
-
⑤ 支持多客户端连接。
-
⑥ 显示或隐藏请求报文。
-
⑦ 显示或隐藏相应报文。
-
⑧ 定时读取功能。
运行环境
- 操作系统:Windows11
- 编程软件:Visual Studio 2022
- Net版本:.Net Framework 4.8.0
预览
(一)运行效果
代码
(一)MainForm代码
- 界面代码大概如下,实现了基本的连接、读取、写入、是否显示报文功能(下面仅是基本的字段和方法 方法签名,完整内容文末自行下载)。
public partial class MainForm : Form { ModbusTcpSlave modbusTcpSlave; ushort startAddress = 0; ushort dataLength = 1; Timer timerRead; public MainForm(){ InitializeComponent(); } private void MainForm_Load(object sender, EventArgs e){} private void MainForm_FormClosing(object sender, FormClosingEventArgs e){} private void Initialize(){} private void TimerRead_Tick(object sender, EventArgs e){} #region 事件方法 #region 按钮事件 private void btn_Connect_Click(object sender, EventArgs e){} private void btn_ReadData_Click(object sender, EventArgs e){} private void btn_SendData_Click(object sender, EventArgs e){} private void btn_ClearMessage_Click(object sender, EventArgs e){} private void btn_ClearSendData_Click(object sender, EventArgs e){} #endregion private void ModbusTcpSlave_MessageCallabck(object sender, ModbusMessageEvents message){} private void ModbusTcpSlave_RequestCallabck(object sender, ModbusMessageEvents message){} private void ModbusTcpSlave_ResponseCallback(object sender, ModbusMessageEvents message){} #endregion #region 控件状态更新 private void ControlStateUpdate(){} private void checkBox_TimedRead_CheckedChanged(object sender, EventArgs e){} #endregion #region 操作消息更新 private void MessageUpdate(string data, Color color, string appendText = null, int maxLineNum = 1000, bool isAppendTime = true){} private void SetTextColor(RichTextBox rtb, int startIndex, int length, Color color){} #endregion #region 参数变更 private void nudx_StartAddress_ValueChanged(object sender, EventArgs e){} private void nudx_DataLength_ValueChanged(object sender, EventArgs e){} private void cbx_FuncCode_SelectedIndexChanged(object sender, EventArgs e){} #endregion }
(二)ModbusTcpSlave代码
- 在这里声明创建从站用到的字段、属性、事件、及相关的方法,相关方法声明如下(下面仅是类的字段和方法,方法签名,完整内容文末自行下载)。
public class ModbusTcpSlave { #region 字段|属性|事件 public event EventHandler<ModbusMessageEvents> MessageCallback; public event EventHandler<ModbusMessageEvents> RequestCallabck; public event EventHandler<ModbusMessageEvents> ResponseCallback; private int _port = 502; private string _address; private int unitId = 1; private bool _isRunning; private ushort[] _holdingRegisters; private readonly int MaxDataCount = 65536; private bool[] _coils; private const int MaxConnections = 5; public int ReceiveTimeout { get; private set; } = 3000; private Thread listenerThread = null; private Socket slaveSocket; private Dictionary<string, Thread> clientThreadList; public bool IsRunning { get => _isRunning; } public string FuncCode { get; set; } = "01"; #endregion #region 构造函数|对象参数初始化 public ModbusTcpSlave(string ipAddress = "127.0.0.1", int port = 502, byte unitId = 1){} private void Initialize(){} #endregion #region 启动|停止 public void Start(){} public void Stop(){} private void ListenClient(){} private void HandleClientConnect(object socket){} #endregion #region 数据转换 public static string ArrayToString<T>(T[] values, string sep = " "){} public static string ArrayToHex<T>(T[] values, string sep = " ") where T : struct, IConvertible{} public static T[] StringToArray<T>(string input, char sep = ','){} public static T[] TryStringToArray<T>(string input, char sep = ','){} private ushort[] ConvertRequestMessage(byte[] request){} #endregion #region 数据变更处理 private byte[] ProcessRequest(byte[] request, int length){} private byte[] HandleReadCoils(byte[] request, ushort startAddress, ushort numberOfPoint){} private byte[] HandleReadHoldingRegisters(byte[] request, ushort startAddress, ushort numberOfPoint){} private byte[] HandleWriteSingleCoil(byte[] request, ushort address, ushort value){} private byte[] HandleWriteSingleRegister(byte[] request, ushort address, ushort value){} private byte[] HandleWriteMultipleCoils(byte[] request, ushort startAddress, ushort numberOfPoint){} private byte[] HandleWriteMultipleRegisters(byte[] request, ushort startAddress, ushort numberOfPoint){} private byte[] CreateErrorResponse(byte[] request, byte errorCode){} #endregion #region 数据变更处理-强制处理 public bool SetSingleCoils(ushort address, bool value){} public bool SetSingleRegister(ushort address, ushort value){} public bool SetMultipleCoils(ushort startAddress, bool[] data){} public bool SetMultipleRegister(ushort startAddress, ushort[] data){} public bool[] GetCoils(ushort address, ushort numberOfPoint){} public ushort[] GetRegisters(ushort address, ushort numberOfPoint){} #endregion #region 回调触发方法 private void OnMessageCallabck(ModbusMessageEvents message){} private void OnRequestCallabck(ModbusMessageEvents message){} private void OnResponseCallabck(ModbusMessageEvents message){} #endregion }
结语
-
通过该项目学习如何使用C# 实现ModbuTCP通讯的从站功能,之前写过使用Socket实现TCP通讯,个人理解从站是在此基础上做了数据保存处理、在发送接收数据时遵从Modbus协议,最后根据情况将数据格式转换,更多功能会在后续添加,写文章是用于记录和分享、在写的过程中加深印象。
-
项目源码 gitee.com/incodenotes/csharp-modbus
-
如果你觉得这篇文章对你有帮助,不妨点个赞再走呗!
-
如有其他疑问,欢迎评论区留言讨论!
-
也可以加入微信公众号 [编程笔记in] ,一起交流学习!