上篇咱们实现了ModBusTcp协议的客户端读写,但是在不少时候编写业务代码以前是没有现场环境的。总不能在客户现场去写代码,或是蒙着眼睛写而后求神拜佛不出错,又或是在办公室部署一套硬件环境。怎么说都感受不太合适,若是咱们能用软件仿真模拟硬件那不就完美了,之后有各类不一样的硬件协议接口都模拟出来,而不是每一个硬件都买一套回来部署了作测试。
真要用软件仿真模拟也是能够的,客户端是对协议的请求报文发送和响应报文的解析,服务端其实就是请求报文的接收和响应报文的发送,正好和客户端的动做相反。
前面咱们在写你也能够写个聊天程序 - C# Socket学习1的时候就有写Socket服务端实现,其实这个也差不了多少。html
协议的理解和实现主要就是要对协议报文理解。(注意:如下报文数据都是十六进制)git
第一次获取前八个字节(Map报文头):19 B2 00 00 00 05 02 03 02 00 20github
第二次获取的报文:02 00 20redis
和请求报文的区别socket
//启动服务 public void Start() { //1 建立Socket对象 var socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2 绑定ip和端口 IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 502); socketServer.Bind(ipEndPoint); //三、开启侦听(等待客户机发出的链接),并设置最大客户端链接数为10 socketServer.Listen(10); Task.Run(() => { Accept(socketServer); }); } //客户端链接到服务端 void Accept(Socket socket) { while (true) { //阻塞等待客户端链接 Socket newSocket = socket.Accept(); Task.Run(() => { Receive(newSocket); }); } }
以上都和咱们前面的同样,这了不同的地方就是对请求报文的解析和响应报文的组装发送tcp
//接收客户端发送的消息 void Receive(Socket newSocket) { while (newSocket.Connected) { byte[] requetData1 = new byte[8]; //读取客户端发送报文 报文头 int readLeng = newSocket.Receive(requetData1, 0, requetData1.Length, SocketFlags.None); byte[] requetData2 = new byte[requetData1[5] - 2]; //读取客户端发送报文 报文数据 readLeng = newSocket.Receive(requetData2, 0, requetData2.Length, SocketFlags.None); var requetData = requetData1.Concat(requetData2).ToArray(); byte[] responseData1 = new byte[8]; //复制请求报文中的报文头 Buffer.BlockCopy(requetData, 0, responseData1, 0, responseData1.Length); //这里能够本身实现一个对象,用来存储客户端写入的数据(也能够用redis等实现数据的持久化) DataPersist data = new DataPersist(""); //根据协议,报文的第八个字节是功能码(前面咱们有说过 03:读保持寄存器 16:写多个寄存器) switch (requetData[7]) { //读保持寄存器 case 3: { var value = data.Read(requetData[9]); short.TryParse(value, out short resultValue); var bytes = BitConverter.GetBytes(resultValue); //当前位置到最后的长度 responseData1[5] = (byte)(3 + bytes.Length); byte[] responseData2 = new byte[3] { (byte)bytes.Length, bytes[1], bytes[0] }; var responseData = responseData1.Concat(responseData2).ToArray(); newSocket.Send(responseData); } break; //写多个寄存器 case 16: { data.Write(requetData[9], requetData[requetData.Length - 1].ToString()); var responseData = new byte[12]; Buffer.BlockCopy(requetData, 0, responseData, 0, responseData.Length); responseData[5] = 6; newSocket.Send(responseData); } break; } } }
这段要点就是根据请求报文得到功能码,而后对报文数据进行读取或写入动做。固然你彻底能够对写入的数据进行持久化存储(如用redis),这样在断电或重启后数据依然能够正常读取。
固然,以上只是个类伪代码,为了减小代码量和方便理解,不少状况和实际可能性没有作对应的处理。学习
Nuget安装 Install-Package IoTClient
或图形化安装
测试
//一、实例化客户端 - 输入正确的IP和端口 ModBusTcpClient client = new ModBusTcpClient("127.0.0.1", 502); //二、读操做 - 参数依次是:地址 、值 、站号 、功能码 client.Write("4", (short)33, 2, 16); client.Write("4", (short)3344, 2, 16); //三、写操做 - 参数依次是:地址 、站号 、功能码 var value = client.ReadInt16("4", 2, 3).Value; var value2 = client.ReadInt32("4", 2, 3).Value; //四、若是没有主动Open,则会每次读写操做的时候自动打开自动和关闭链接,这样会使读写效率大大减低。因此建议手动Open和Close。 client.Open(); //五、读写操做都会返回操做结果对象Result var result = client.ReadInt16("4", 2, 3); //5.1 读取是否成功(true或false) var isSucceed = result.IsSucceed; //5.2 读取失败的异常信息 var errMsg = result.Err; //5.3 读取操做实际发送的请求报文 var requst = result.Requst; //5.4 读取操做服务端响应的报文 var response = result.Response; //5.5 读取到的值 var value3 = result.Value;