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;
}
}
}
}
}
}
}