ModbusTcpSlaveNetwork.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using System.Net.Sockets;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using NModbus.IO;
  9. using NModbus.Logging;
  10. namespace NModbus.Device
  11. {
  12. #if TIMER
  13. using System.Timers;
  14. #endif
  15. /// <summary>
  16. /// Modbus TCP slave device.
  17. /// </summary>
  18. public class ModbusTcpSlaveNetwork : ModbusSlaveNetwork, IModbusTcpSlaveNetwork
  19. {
  20. private const int TimeWaitResponse = 1000;
  21. private readonly object _serverLock = new object();
  22. private readonly ConcurrentDictionary<string, ModbusMasterTcpConnection> _masters =
  23. new ConcurrentDictionary<string, ModbusMasterTcpConnection>();
  24. private TcpListener _server;
  25. #if TIMER
  26. private Timer _timer;
  27. #endif
  28. public ModbusTcpSlaveNetwork(TcpListener tcpListener, IModbusFactory modbusFactory, IModbusLogger logger)
  29. : base(new EmptyTransport(modbusFactory), modbusFactory, logger)
  30. {
  31. if (tcpListener == null)
  32. {
  33. throw new ArgumentNullException(nameof(tcpListener));
  34. }
  35. _server = tcpListener;
  36. }
  37. #if TIMER
  38. private ModbusTcpSlave(byte unitId, TcpListener tcpListener, double timeInterval)
  39. : base(unitId, new EmptyTransport())
  40. {
  41. if (tcpListener == null)
  42. {
  43. throw new ArgumentNullException(nameof(tcpListener));
  44. }
  45. _server = tcpListener;
  46. _timer = new Timer(timeInterval);
  47. _timer.Elapsed += OnTimer;
  48. _timer.Enabled = true;
  49. }
  50. #endif
  51. /// <summary>
  52. /// Gets the Modbus TCP Masters connected to this Modbus TCP Slave.
  53. /// </summary>
  54. public ReadOnlyCollection<TcpClient> Masters
  55. {
  56. get
  57. {
  58. return new ReadOnlyCollection<TcpClient>(_masters.Values.Select(mc => mc.TcpClient).ToList());
  59. }
  60. }
  61. /// <summary>
  62. /// Gets the server.
  63. /// </summary>
  64. /// <value>The server.</value>
  65. /// <remarks>
  66. /// This property is not thread safe, it should only be consumed within a lock.
  67. /// </remarks>
  68. private TcpListener Server
  69. {
  70. get
  71. {
  72. if (_server == null)
  73. {
  74. throw new ObjectDisposedException("Server");
  75. }
  76. return _server;
  77. }
  78. }
  79. ///// <summary>
  80. ///// Modbus TCP slave factory method.
  81. ///// </summary>
  82. //public static ModbusTcpSlave CreateTcp(byte unitId, TcpListener tcpListener)
  83. //{
  84. // return new ModbusTcpSlave(unitId, tcpListener);
  85. //}
  86. #if TIMER
  87. /// <summary>
  88. /// Creates ModbusTcpSlave with timer which polls connected clients every
  89. /// <paramref name="pollInterval"/> milliseconds on that they are connected.
  90. /// </summary>
  91. public static ModbusTcpSlave CreateTcp(byte unitId, TcpListener tcpListener, double pollInterval)
  92. {
  93. return new ModbusTcpSlave(unitId, tcpListener, pollInterval);
  94. }
  95. #endif
  96. /// <summary>
  97. /// Start slave listening for requests.
  98. /// </summary>
  99. public override async Task ListenAsync(CancellationToken cancellationToken = new CancellationToken())
  100. {
  101. Logger.Information("Start Modbus Tcp Server.");
  102. // TODO: add state {stopped, listening} and check it before starting
  103. Server.Start();
  104. // Cancellation code based on https://stackoverflow.com/a/47049129/11066760
  105. using (cancellationToken.Register(() => Server.Stop()))
  106. {
  107. try
  108. {
  109. while (!cancellationToken.IsCancellationRequested)
  110. {
  111. TcpClient client = await Server.AcceptTcpClientAsync().ConfigureAwait(false);
  112. var masterConnection = new ModbusMasterTcpConnection(client, this, ModbusFactory, Logger);
  113. masterConnection.ModbusMasterTcpConnectionClosed += OnMasterConnectionClosedHandler;
  114. _masters.TryAdd(client.Client.RemoteEndPoint.ToString(), masterConnection);
  115. }
  116. }
  117. catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
  118. {
  119. //Swallow this
  120. }
  121. catch (InvalidOperationException)
  122. {
  123. // Either Server.Start wasn't called (a bug!)
  124. // or the CancellationToken was cancelled before
  125. // we started accepting (giving an InvalidOperationException),
  126. // or the CancellationToken was cancelled after
  127. // we started accepting (giving an ObjectDisposedException).
  128. //
  129. // In the latter two cases we should surface the cancellation
  130. // exception, or otherwise rethrow the original exception.
  131. cancellationToken.ThrowIfCancellationRequested();
  132. throw;
  133. }
  134. }
  135. }
  136. /// <summary>
  137. /// Releases unmanaged and - optionally - managed resources
  138. /// </summary>
  139. /// <param name="disposing">
  140. /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only
  141. /// unmanaged resources.
  142. /// </param>
  143. /// <remarks>Dispose is thread-safe.</remarks>
  144. protected override void Dispose(bool disposing)
  145. {
  146. if (disposing)
  147. {
  148. // double-check locking
  149. if (_server != null)
  150. {
  151. lock (_serverLock)
  152. {
  153. if (_server != null)
  154. {
  155. _server.Stop();
  156. _server = null;
  157. #if TIMER
  158. if (_timer != null)
  159. {
  160. _timer.Dispose();
  161. _timer = null;
  162. }
  163. #endif
  164. foreach (var key in _masters.Keys)
  165. {
  166. ModbusMasterTcpConnection connection;
  167. if (_masters.TryRemove(key, out connection))
  168. {
  169. connection.ModbusMasterTcpConnectionClosed -= OnMasterConnectionClosedHandler;
  170. connection.Dispose();
  171. }
  172. }
  173. }
  174. }
  175. }
  176. }
  177. }
  178. private static bool IsSocketConnected(Socket socket)
  179. {
  180. bool poll = socket.Poll(TimeWaitResponse, SelectMode.SelectRead);
  181. bool available = (socket.Available == 0);
  182. return poll && available;
  183. }
  184. #if TIMER
  185. private void OnTimer(object sender, ElapsedEventArgs e)
  186. {
  187. foreach (var master in _masters.ToList())
  188. {
  189. if (IsSocketConnected(master.Value.TcpClient.Client) == false)
  190. {
  191. master.Value.Dispose();
  192. }
  193. }
  194. }
  195. #endif
  196. private void OnMasterConnectionClosedHandler(object sender, TcpConnectionEventArgs e)
  197. {
  198. ModbusMasterTcpConnection connection;
  199. if (!_masters.TryRemove(e.EndPoint, out connection))
  200. {
  201. string msg = $"EndPoint {e.EndPoint} cannot be removed, it does not exist.";
  202. throw new ArgumentException(msg);
  203. }
  204. connection.Dispose();
  205. Logger.Information($"Removed Master {e.EndPoint}");
  206. }
  207. }
  208. }