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 ...
随机推荐
- Linux进程管理 (9)实时调度类分析,以及FIFO和RR对比实验
关键词:rt_sched_class.SCHED_FIFO.SCHED_RR.sched_setscheduler().sched_setaffinity().RR_TIMESLICE. 本文主要关注 ...
- java基础 容器 API
- mybatis-高级结果映射之一对多
目录 1 数据准备 1.2 实体类, 接口和XML 2 一对多映射 2.1 collection集合映射 2.1.1 创建结果实体类 2.1.2 创建结果集 2.1.3 创建对应的方法和XML 2.1 ...
- 移动前端webApp开发点滴积累20140524
#webApp开发几点体会(移动前端) ##前言 本文旨在记录本人涉足移动webApp开发的几点体会,欢迎分享与指正. ##再见,IE678 移动设备,Android跟iPhone是主流,即使是win ...
- 剑指offer 第十一天
46.扑克牌顺子 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的 ...
- 05 Docker集群/基础设施 - DevOps之路
05 Docker集群/基础设施 - DevOps之路 文章Github地址,欢迎start:https://github.com/li-keli/DevOps-WiKi Docker的集群目前主流的 ...
- 我的微信小程序第二篇
在上一篇<我的微信小程序第一篇(入门)>中,很多人问我什么是微信小程序,在这里我要说一下这个是我的失误啦,我默认大家都知道微信小程序,其实可能行内人士都知道小程序,好多非行内朋友可能平时不 ...
- vue2.0之axios使用详解
axios 基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 node.js 中使用 功能特性 在浏览器中发送 XMLHttpRequests 请求 在 node.js 中发送 htt ...
- 广州商学院16级软工一班&二班-第一次作业成绩
广州商学院16级软工一班&二班-第一次作业成绩 作业地址 16软工一班 16软工二班 总结 本次作业反映了几个比较严重的问题: 不按要求阅读相应的文章,回答问题只是敷衍几句. 部分同学的版式混 ...
- C语言之运算符、表达式和语句
#include<stdio.h> #define ADJUST 7.31 int main(void) { const double SCALE = 0.333; double shoe ...