本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议

Client

public class ModbusClient
{
public string Ip { get; }
public int Port { get; }
public short UnitIdentifier { get; }
public IChannel Channel { get; private set; } private MultithreadEventLoopGroup group;
private ConnectionState connectionState;
private ushort transactionIdentifier;
private readonly string handlerName = "response"; public ModbusClient(short unitIdentifier, string ip, int port = 502)
{
Ip = ip;
Port = port;
UnitIdentifier = unitIdentifier; connectionState = ConnectionState.NotConnected;
} public async Task Connect()
{
group = new MultithreadEventLoopGroup(); try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline; pipeline.AddLast("encoder", new ModbusEncoder());
pipeline.AddLast("decoder", new ModbusDecoder(false)); pipeline.AddLast(handlerName, new ModbusResponseHandler());
})); connectionState = ConnectionState.Pending; Channel = await bootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse(Ip), Port)); connectionState = ConnectionState.Connected;
}
catch (Exception exception)
{
throw exception;
}
} public async Task Close()
{
if (ConnectionState.Connected == connectionState)
{
try
{
await Channel.CloseAsync();
}
finally
{
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); connectionState = ConnectionState.NotConnected;
}
}
} public ushort CallModbusFunction(ModbusFunction function)
{
if (ConnectionState.Connected != connectionState || Channel == null)
{
throw new Exception("Not connected!");
} SetTransactionIdentifier(); ModbusHeader header = new ModbusHeader(transactionIdentifier, UnitIdentifier);
ModbusFrame frame = new ModbusFrame(header, function);
Channel.WriteAndFlushAsync(frame); return transactionIdentifier;
} public T CallModbusFunctionSync<T>(ModbusFunction function) where T : ModbusFunction
{
var transactionIdentifier = CallModbusFunction(function); var handler = (ModbusResponseHandler)Channel.Pipeline.Get(handlerName);
if (handler == null)
{
throw new Exception("Not connected!");
} return (T)handler.GetResponse(transactionIdentifier).Function;
} private void SetTransactionIdentifier()
{
if (transactionIdentifier < ushort.MaxValue)
{
transactionIdentifier++;
}
else
{
transactionIdentifier = 1;
}
} public ushort ReadHoldingRegistersAsync(ushort startingAddress, ushort quantity)
{
var function = new ReadHoldingRegistersRequest(startingAddress, quantity);
return CallModbusFunction(function);
} public ReadHoldingRegistersResponse ReadHoldingRegisters(ushort startingAddress, ushort quantity)
{
var function = new ReadHoldingRegistersRequest(startingAddress, quantity);
return CallModbusFunctionSync<ReadHoldingRegistersResponse>(function);
}
} public enum ConnectionState
{
NotConnected = 0,
Connected = 1,
Pending = 2,
}

(文中代码仅添加了 0x03 的方法)

在 Client 中封装了 Modbus 请求方法,对同一个功能同时有同步方法(ReadHoldingRegistersAsync)和异步方法(ReadHoldingRegisters)。同步方法仅返回 TransactionIdentifier(传输标识),异步方法返回响应结果。

ModbusResponseHandler 修改为:

public class ModbusResponseHandler : SimpleChannelInboundHandler<ModbusFrame>
{
private readonly int timeoutMilliseconds = 2000;
private Dictionary<ushort, ModbusFrame> responses = new Dictionary<ushort, ModbusFrame>();
protected override void ChannelRead0(IChannelHandlerContext ctx, ModbusFrame msg)
{
responses.Add(msg.Header.TransactionIdentifier, msg);
} public ModbusFrame GetResponse(ushort transactionIdentifier)
{
ModbusFrame frame = null;
var timeoutDateTime = DateTime.Now.AddMilliseconds(timeoutMilliseconds);
do
{
Thread.Sleep(1);
if (responses.ContainsKey(transactionIdentifier))
{
frame = responses[transactionIdentifier];
responses.Remove(transactionIdentifier);
}
}
while (frame == null && DateTime.Now < timeoutDateTime); if(frame == null)
{
throw new Exception("No Response");
}
else if(frame.Function is ExceptionFunction)
{
throw new Exception(frame.Function.ToString());
} return frame;
} public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
context.CloseAsync();
}
}

Server

public class ModbusServer
{
private ModbusResponseService responseService;
private ServerState serverState;
public int Port { get; }
public IChannel Channel { get; private set; }
private IEventLoopGroup bossGroup;
private IEventLoopGroup workerGroup;
public ModbusServer(ModbusResponseService responseService, int port = 502)
{
this.responseService = responseService;
Port = port;
serverState = ServerState.NotStarted;
} public async Task Start()
{
bossGroup = new MultithreadEventLoopGroup(1);
workerGroup = new MultithreadEventLoopGroup(); try
{
var bootstrap = new ServerBootstrap();
bootstrap.Group(bossGroup, workerGroup); bootstrap
.Channel<TcpServerSocketChannel>()
.Option(ChannelOption.SoBacklog, 100)
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("encoder", new ModbusEncoder());
pipeline.AddLast("decoder", new ModbusDecoder(true)); pipeline.AddLast("request", new ModbusRequestHandler(responseService));
})); serverState = ServerState.Starting; Channel = await bootstrap.BindAsync(Port); serverState = ServerState.Started;
}
finally
{ }
} public async Task Stop()
{
if (ServerState.Starting == serverState)
{
try
{
await Channel.CloseAsync();
}
finally
{
await Task.WhenAll(
bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))); serverState = ServerState.NotStarted;
}
}
}
} public enum ServerState
{
NotStarted = 0,
Started = 1,
Starting = 2,
}

实例化 Server 时需要传入 ModbusResponseService 的实现,实现示例:

public class ModbusResponse : ModbusResponseService
{
public override ModbusFunction ReadHoldingRegisters(ReadHoldingRegistersRequest request)
{
var registers = ReadRegisters(request.Quantity);
var response = new ReadHoldingRegistersResponse(registers); return response;
} private ushort[] ReadRegisters(ushort quantity)
{
var registers = new ushort[quantity]; Random ran = new Random();
for (int i = 0; i < registers.Length; i++)
{
registers[i] = (ushort)ran.Next(ushort.MinValue, ushort.MaxValue);
} return registers;
}
}

(文中代码仅添加了 0x03 的方法)

开源地址:modbus-tcp

DotNetty 实现 Modbus TCP 系列 (四) Client & Server的更多相关文章

  1. DotNetty 实现 Modbus TCP 系列 (三) Codecs & Handler

    本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 DotNetty 作为一个半成品,我们不需要关注细节的实现,只需要关注自己的业务即可,所以最主要的就是处理 Codecs ...

  2. DotNetty 实现 Modbus TCP 系列 (二) ModbusFunction 类图及继承举例

    本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 ModbusFunction 类图如下: 如前文所述,所有请求/相应的 PDU 均继承自 ModbusFunction, ...

  3. DotNetty 实现 Modbus TCP 系列 (一) 报文类

    本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 Modbus TCP/IP 报文 报文最大长度为 260 byte (ADU = 7 byte MBAP Header ...

  4. 开源 DotNetty 实现的 Modbus TCP/IP 协议

    本项目的目的是为了学习 DotNetty 与 Modbus 协议,参考 modjn 实现功能 0x01: Read Coils (读取线圈/离散量输出状态) 0x02: Read Discrete I ...

  5. SQL Server 2008空间数据应用系列四:基础空间对象与函数应用

    原文:SQL Server 2008空间数据应用系列四:基础空间对象与函数应用 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. ...

  6. Modbus库开发笔记之九:利用协议栈开发Modbus TCP Server应用

    前面我们已经完成了Modbus协议栈的开发,但这不是我们的目的.我们开发它的目的当然是要使用它来解决我们的实际问题.接下来我们就使用刚开发的Modbus协议栈开发一个Modbus TCP Server ...

  7. Modbus库开发笔记之四:Modbus TCP Client开发

    这一次我们封装Modbus TCP Client应用.同样的我们也不是做具体的应用,而是实现TCP客户端的基本功能.我们将TCP客户端的功能封装为函数,以便在开发具体应用时调用. 对于TCP客户端我们 ...

  8. Modbus库开发笔记之三:Modbus TCP Server开发

    在完成了前面的工作后,我们就可以实现有针对性的应用了,首先我们来实现Modbus TCP的服务器端应用.当然我们不是做具体的应用,而是对Modbus TCP的服务器端应用进行封装以供有需要时调用. 这 ...

  9. TCP/UDP通信中server和client是如何知道对方IP地址的

    在TCP通信中 client是主动连接的一方,client对server的IP的地址提前已知的.如果是未知则是没办法通信的. server是在accpet返回的时候知道的,因为数据包中包含客户端的IP ...

随机推荐

  1. 获取数值型数组的最大值和最小值,使用遍历获取每一个值,然后记录最大值和最小值的方式。(数组遍历嵌套if判断语句)

    package com.Summer_0420.cn; /** * @author Summer * .获取数值型数组的最大值.最小值 * 方法:遍历获取每一个值,记录最大值: * 方法:遍历获取每一 ...

  2. Linux VNC安装

    cat /etc/centos-release CentOS Linux release 7.4.1708 (Core) uname -r 3.10.0-693.el7.x86_64 VNC下载:ht ...

  3. AI caffe

    caffe,是一个深度学习框架. Blob 存储数据. 批量图像数据(n, k, h, w):图像数量,图像通道,图像高度,图像宽度. data:值 diff:梯度 Layer data_layers ...

  4. Mybatis学习总结(九)——查询缓存

    一.什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能.mybaits提供一级缓存和二级缓存. 1.一级缓存是sqlSession级别的缓存.在操作数据库时需要构造sqlSe ...

  5. 本地搭了http服务(localhost),怎么在vue环境下,通过axios获取到接口数据

    1. 找到 vue项目\config\index.js 文件 2. proxyTable: { '/api': { target: 'http://127.0.0.1:9420', changeOri ...

  6. [03] SpringBoot+MyBatis+Shiro搭建杂谈

    0.写在前面的话 一直想能仿公司框架的形式,着手做一个简单的脚手架,一来是带着目标性能更好地学习,接触新的技术,另外自己如果有什么想要实现的简单需求,就可以进行快速开发,主要还是希望能在权限上有所控制 ...

  7. 使用PHPExcel解析Excel表格

    安装类库 从GitHub上下载PHPExcel类库 地址:https://github.com/PHPOffice/PHPExcel 解压后将Classes文件夹移动到ThinkPHP的extend目 ...

  8. Appium+Python3+iOS定位元素

    前言: 最近在做IOS自动化测试,IOS的Appium环境都配置OK,执行起来真的慢,慢到怀疑人生,那么今天就来总结一下IOS定位方式和各个定位方式的速度排序. 据我观察,按查找元素的顺序速度,从快到 ...

  9. python第四章:函数--小白博客

    Python函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可 ...

  10. awk分析mysql状态

    今天是腊月27,明天是腊月28,一到过年,就习惯说农历,而不说公历.这两天挺闲的,就再造一把. 话说Linux处理文本工具有三剑客,awk.grep.sed,其中awk最为厉害,grep也挺是常用.今 ...