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 ...
随机推荐
- 红米Note5进入全网通5.0时代,其实是高通已经落后了!
高通早在去年12月份就正式发布了新一代的骁龙845处理器,接下来就是人们对于搭载骁龙845处理器的手机充满期待,可是转眼到了2018年的3月份,目前已经发布的高端旗舰新机却只有三星S9和三星S9+,而 ...
- Linux中断管理 (1)Linux中断管理机制
目录: <Linux中断管理> <Linux中断管理 (1)Linux中断管理机制> <Linux中断管理 (2)软中断和tasklet> <Linux中断管 ...
- WPF效果(GIS三维篇)
二维的GIS已经被我玩烂了,紧接着就是三维了,哈哈!先来看看最简单的效果:
- js 对象与数组相互转化的快捷方法 Object.keys()、Object.values()、Object.entries()
Object.keys() Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性.这些属性的顺序与手动遍历该对象属性时的一致. 例如: let ...
- HBuilder使用夜神模拟器调试Android应用
由于HBuilder的扫描机制无法直接连上夜神模拟器.我搞了好久终于找到办法了,分享给大家. 首先,启动HBuilder和夜神模拟器 然后打开cmd命令提示符 cd进入夜神模拟器bin目录 执行以下命 ...
- 教你使用HTML5原生对话框元素,轻松创建模态框组件
HTML 5.2草案加入了新的dialog元素.但是是一种实验技术. 以前,如果我们想要构建任何形式的模式对话框或对话框,我们需要有一个背景,一个关闭按钮,将事件绑定在对话框中的方式安排我们的标记,找 ...
- C#使用ILGenerator动态生成函数
游戏服务器里面总是有一大堆的配置文件需要读取, 而且这些配置文件的读取: * 要不然做成弱类型的, 就是一堆字符串或者数字, 不能看出来错误(需要重新检测一次) * 要不然做成强类型的, 每种类型都需 ...
- python爬虫随笔-scrapy框架(1)——scrapy框架的安装和结构介绍
scrapy框架简介 Scrapy,Python开发的一个快速.高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据.Scrapy用途广泛,可以用于数据挖掘.监测和自动化测试 ...
- A short Glimpse to Spectral Sequences 快速入坑谱序列(英文)
In this short paper with a few of page (but a lot of language mistake), I want to introduce the theo ...
- html中怎么设置性别默认选择
<html><body> <form action="/example/html/form_action.asp" method="get& ...