using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Net.Sockets; using log4net; using Modbus.IO; using Modbus.Utility; namespace Modbus.Device { /// /// Modbus TCP slave device. /// public class ModbusTcpSlave : ModbusSlave { private readonly object _mastersLock = new object(); private readonly object _serverLock = new object(); private readonly ILog _logger = LogManager.GetLogger(typeof(ModbusTcpSlave)); private readonly Dictionary _masters = new Dictionary(); private TcpListener _server; private ModbusTcpSlave(byte unitId, TcpListener tcpListener) : base(unitId, new EmptyTransport()) { if (tcpListener == null) throw new ArgumentNullException("tcpListener"); _server = tcpListener; } /// /// Gets the Modbus TCP Masters connected to this Modbus TCP Slave. /// public ReadOnlyCollection Masters { get { lock (_mastersLock) { return new ReadOnlyCollection( SequenceUtility.ToList(_masters.Values, delegate(ModbusMasterTcpConnection connection) { return connection.TcpClient; })); } } } /// /// Gets the server. /// /// The server. /// /// This property is not thread safe, it should only be consumed within a lock. /// private TcpListener Server { get { if (_server == null) throw new ObjectDisposedException("Server"); return _server; } } /// /// Modbus TCP slave factory method. /// public static ModbusTcpSlave CreateTcp(byte unitId, TcpListener tcpListener) { return new ModbusTcpSlave(unitId, tcpListener); } /// /// Start slave listening for requests. /// public override void Listen() { _logger.Debug("Start Modbus Tcp Server."); lock (_serverLock) { try { Server.Start(); // use Socket async API for compact framework compat Server.Server.BeginAccept(AcceptCompleted, this); } catch (ObjectDisposedException) { // this happens when the server stops } } } internal void RemoveMaster(string endPoint) { lock (_mastersLock) { if (!_masters.Remove(endPoint)) throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "EndPoint {0} cannot be removed, it does not exist.", endPoint)); } _logger.InfoFormat("Removed Master {0}", endPoint); } internal void AcceptCompleted(IAsyncResult ar) { ModbusTcpSlave slave = (ModbusTcpSlave) ar.AsyncState; try { // use Socket async API for compact framework compat Socket socket = null; lock (_serverLock) socket = Server.Server.EndAccept(ar); TcpClient client = new TcpClient { Client = socket }; var masterConnection = new ModbusMasterTcpConnection(client, slave); masterConnection.ModbusMasterTcpConnectionClosed += (sender, eventArgs) => RemoveMaster(eventArgs.EndPoint); lock (_mastersLock) _masters.Add(client.Client.RemoteEndPoint.ToString(), masterConnection); _logger.Debug("Accept completed."); // Accept another client // use Socket async API for compact framework compat lock (_serverLock) Server.Server.BeginAccept(AcceptCompleted, slave); } catch (ObjectDisposedException) { // this happens when the server stops } } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. /// Dispose is thread-safe. protected override void Dispose(bool disposing) { if (disposing) { // double-check locking if (_server != null) { lock (_serverLock) { if (_server != null) { _server.Stop(); _server = null; } } } } } } }