DotNetty 实现 Modbus TCP 系列 (四) Client & Server
本文已收录至:开源 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的更多相关文章
- DotNetty 实现 Modbus TCP 系列 (三) Codecs & Handler
本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 DotNetty 作为一个半成品,我们不需要关注细节的实现,只需要关注自己的业务即可,所以最主要的就是处理 Codecs ...
- DotNetty 实现 Modbus TCP 系列 (二) ModbusFunction 类图及继承举例
本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 ModbusFunction 类图如下: 如前文所述,所有请求/相应的 PDU 均继承自 ModbusFunction, ...
- DotNetty 实现 Modbus TCP 系列 (一) 报文类
本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议 Modbus TCP/IP 报文 报文最大长度为 260 byte (ADU = 7 byte MBAP Header ...
- 开源 DotNetty 实现的 Modbus TCP/IP 协议
本项目的目的是为了学习 DotNetty 与 Modbus 协议,参考 modjn 实现功能 0x01: Read Coils (读取线圈/离散量输出状态) 0x02: Read Discrete I ...
- SQL Server 2008空间数据应用系列四:基础空间对象与函数应用
原文:SQL Server 2008空间数据应用系列四:基础空间对象与函数应用 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. ...
- Modbus库开发笔记之九:利用协议栈开发Modbus TCP Server应用
前面我们已经完成了Modbus协议栈的开发,但这不是我们的目的.我们开发它的目的当然是要使用它来解决我们的实际问题.接下来我们就使用刚开发的Modbus协议栈开发一个Modbus TCP Server ...
- Modbus库开发笔记之四:Modbus TCP Client开发
这一次我们封装Modbus TCP Client应用.同样的我们也不是做具体的应用,而是实现TCP客户端的基本功能.我们将TCP客户端的功能封装为函数,以便在开发具体应用时调用. 对于TCP客户端我们 ...
- Modbus库开发笔记之三:Modbus TCP Server开发
在完成了前面的工作后,我们就可以实现有针对性的应用了,首先我们来实现Modbus TCP的服务器端应用.当然我们不是做具体的应用,而是对Modbus TCP的服务器端应用进行封装以供有需要时调用. 这 ...
- TCP/UDP通信中server和client是如何知道对方IP地址的
在TCP通信中 client是主动连接的一方,client对server的IP的地址提前已知的.如果是未知则是没办法通信的. server是在accpet返回的时候知道的,因为数据包中包含客户端的IP ...
随机推荐
- 获取数值型数组的最大值和最小值,使用遍历获取每一个值,然后记录最大值和最小值的方式。(数组遍历嵌套if判断语句)
package com.Summer_0420.cn; /** * @author Summer * .获取数值型数组的最大值.最小值 * 方法:遍历获取每一个值,记录最大值: * 方法:遍历获取每一 ...
- Linux VNC安装
cat /etc/centos-release CentOS Linux release 7.4.1708 (Core) uname -r 3.10.0-693.el7.x86_64 VNC下载:ht ...
- AI caffe
caffe,是一个深度学习框架. Blob 存储数据. 批量图像数据(n, k, h, w):图像数量,图像通道,图像高度,图像宽度. data:值 diff:梯度 Layer data_layers ...
- Mybatis学习总结(九)——查询缓存
一.什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能.mybaits提供一级缓存和二级缓存. 1.一级缓存是sqlSession级别的缓存.在操作数据库时需要构造sqlSe ...
- 本地搭了http服务(localhost),怎么在vue环境下,通过axios获取到接口数据
1. 找到 vue项目\config\index.js 文件 2. proxyTable: { '/api': { target: 'http://127.0.0.1:9420', changeOri ...
- [03] SpringBoot+MyBatis+Shiro搭建杂谈
0.写在前面的话 一直想能仿公司框架的形式,着手做一个简单的脚手架,一来是带着目标性能更好地学习,接触新的技术,另外自己如果有什么想要实现的简单需求,就可以进行快速开发,主要还是希望能在权限上有所控制 ...
- 使用PHPExcel解析Excel表格
安装类库 从GitHub上下载PHPExcel类库 地址:https://github.com/PHPOffice/PHPExcel 解压后将Classes文件夹移动到ThinkPHP的extend目 ...
- Appium+Python3+iOS定位元素
前言: 最近在做IOS自动化测试,IOS的Appium环境都配置OK,执行起来真的慢,慢到怀疑人生,那么今天就来总结一下IOS定位方式和各个定位方式的速度排序. 据我观察,按查找元素的顺序速度,从快到 ...
- python第四章:函数--小白博客
Python函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可 ...
- awk分析mysql状态
今天是腊月27,明天是腊月28,一到过年,就习惯说农历,而不说公历.这两天挺闲的,就再造一把. 话说Linux处理文本工具有三剑客,awk.grep.sed,其中awk最为厉害,grep也挺是常用.今 ...