ModbusMasterTcpConnection.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Threading.Tasks;
  8. using NModbus.IO;
  9. using NModbus.Message;
  10. using NModbus.Logging;
  11. namespace NModbus.Device
  12. {
  13. using Extensions;
  14. using System.Diagnostics.CodeAnalysis;
  15. /// <summary>
  16. /// Represents an incoming connection from a Modbus master. Contains the slave's logic to process the connection.
  17. /// </summary>
  18. internal class ModbusMasterTcpConnection : ModbusDevice, IDisposable
  19. {
  20. private readonly TcpClient _client;
  21. private readonly string _endPoint=string.Empty;
  22. private readonly Stream _stream;
  23. private readonly IModbusSlaveNetwork _slaveNetwork;
  24. private readonly IModbusFactory _modbusFactory;
  25. private readonly Task _requestHandlerTask;
  26. private readonly byte[] _mbapHeader = new byte[6];
  27. private byte[] _messageFrame = new byte[0];
  28. public ModbusMasterTcpConnection(TcpClient client, IModbusSlaveNetwork slaveNetwork, IModbusFactory modbusFactory, IModbusLogger logger)
  29. : base(new ModbusIpTransport(new TcpClientAdapter(client), modbusFactory, logger))
  30. {
  31. Logger = logger ?? throw new ArgumentNullException(nameof(logger));
  32. _client = client ?? throw new ArgumentNullException(nameof(client));
  33. _endPoint = client.Client?.RemoteEndPoint?.ToString() ?? string.Empty;
  34. _stream = client.GetStream();
  35. _slaveNetwork = slaveNetwork ?? throw new ArgumentNullException(nameof(slaveNetwork));
  36. _modbusFactory = modbusFactory ?? throw new ArgumentNullException(nameof(modbusFactory));
  37. _requestHandlerTask = Task.Run((Func<Task>)HandleRequestAsync);
  38. }
  39. /// <summary>
  40. /// Occurs when a Modbus master TCP connection is closed.
  41. /// </summary>
  42. public event EventHandler<TcpConnectionEventArgs> ModbusMasterTcpConnectionClosed
  43. {
  44. add
  45. {
  46. _modbusMasterTcpConnectionClosed += value;
  47. }
  48. remove
  49. {
  50. _modbusMasterTcpConnectionClosed -= value;
  51. }
  52. }
  53. private event EventHandler<TcpConnectionEventArgs> _modbusMasterTcpConnectionClosed;
  54. public IModbusLogger Logger { get; }
  55. public string EndPoint => _endPoint;
  56. public Stream Stream => _stream;
  57. public TcpClient TcpClient => _client;
  58. protected override void Dispose(bool disposing)
  59. {
  60. if (disposing)
  61. {
  62. _stream.Dispose();
  63. }
  64. base.Dispose(disposing);
  65. }
  66. private async Task HandleRequestAsync()
  67. {
  68. try
  69. {
  70. while (true)
  71. {
  72. Logger.Debug($"Begin reading header from Master at IP: {EndPoint}");
  73. int readBytes = await Stream.ReadAsync(_mbapHeader, 0, 6).ConfigureAwait(false);
  74. if (readBytes == 0)
  75. {
  76. Logger.Debug($"0 bytes read, Master at {EndPoint} has closed Socket connection.");
  77. _modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
  78. return;
  79. }
  80. ushort frameLength = (ushort)IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_mbapHeader, 4));
  81. Logger.Debug($"Master at {EndPoint} sent header: \"{string.Join(", ", _mbapHeader)}\" with {frameLength} bytes in PDU");
  82. _messageFrame = new byte[frameLength];
  83. readBytes = await Stream.ReadAsync(_messageFrame, 0, frameLength).ConfigureAwait(false);
  84. if (readBytes == 0)
  85. {
  86. Logger.Debug($"0 bytes read, Master at {EndPoint} has closed Socket connection.");
  87. _modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
  88. return;
  89. }
  90. Logger.Debug($"Read frame from Master at {EndPoint} completed {readBytes} bytes");
  91. byte[] frame = _mbapHeader.Concat(_messageFrame).ToArray();
  92. Logger.Trace($"RX from Master at {EndPoint}: {string.Join(", ", frame)}");
  93. var request = _modbusFactory.CreateModbusRequest(_messageFrame);
  94. request.TransactionId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 0));
  95. IModbusSlave slave = _slaveNetwork.GetSlave(request.SlaveAddress);
  96. if (slave != null)
  97. {
  98. //TODO: Determine if this is appropriate
  99. // perform action and build response
  100. IModbusMessage response = slave.ApplyRequest(request);
  101. response.TransactionId = request.TransactionId;
  102. // write response
  103. byte[] responseFrame = Transport.BuildMessageFrame(response);
  104. Logger.Information($"TX to Master at {EndPoint}: {string.Join(", ", responseFrame)}");
  105. await Stream.WriteAsync(responseFrame, 0, responseFrame.Length).ConfigureAwait(false);
  106. }
  107. }
  108. }
  109. // If an exception occurs (such as IOException in case of disconnect, or other failures), handle it as if the connection was gracefully closed
  110. catch(Exception e)
  111. {
  112. Logger.Warning($"{e.GetType().Name} occured with Master at {EndPoint}. Closing connection.");
  113. _modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
  114. return;
  115. }
  116. }
  117. }
  118. }