本文已收录至:开源 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 修改为:
