using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using NModbus.IO;
using NModbus.Message;
using NModbus.Logging;
namespace NModbus.Device
{
using Extensions;
using System.Diagnostics.CodeAnalysis;
///
/// Represents an incoming connection from a Modbus master. Contains the slave's logic to process the connection.
///
internal class ModbusMasterTcpConnection : ModbusDevice, IDisposable
{
private readonly TcpClient _client;
private readonly string _endPoint=string.Empty;
private readonly Stream _stream;
private readonly IModbusSlaveNetwork _slaveNetwork;
private readonly IModbusFactory _modbusFactory;
private readonly Task _requestHandlerTask;
private readonly byte[] _mbapHeader = new byte[6];
private byte[] _messageFrame = new byte[0];
public ModbusMasterTcpConnection(TcpClient client, IModbusSlaveNetwork slaveNetwork, IModbusFactory modbusFactory, IModbusLogger logger)
: base(new ModbusIpTransport(new TcpClientAdapter(client), modbusFactory, logger))
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
_client = client ?? throw new ArgumentNullException(nameof(client));
_endPoint = client.Client?.RemoteEndPoint?.ToString() ?? string.Empty;
_stream = client.GetStream();
_slaveNetwork = slaveNetwork ?? throw new ArgumentNullException(nameof(slaveNetwork));
_modbusFactory = modbusFactory ?? throw new ArgumentNullException(nameof(modbusFactory));
_requestHandlerTask = Task.Run((Func)HandleRequestAsync);
}
///
/// Occurs when a Modbus master TCP connection is closed.
///
public event EventHandler ModbusMasterTcpConnectionClosed
{
add
{
_modbusMasterTcpConnectionClosed += value;
}
remove
{
_modbusMasterTcpConnectionClosed -= value;
}
}
private event EventHandler _modbusMasterTcpConnectionClosed;
public IModbusLogger Logger { get; }
public string EndPoint => _endPoint;
public Stream Stream => _stream;
public TcpClient TcpClient => _client;
protected override void Dispose(bool disposing)
{
if (disposing)
{
_stream.Dispose();
}
base.Dispose(disposing);
}
private async Task HandleRequestAsync()
{
try
{
while (true)
{
Logger.Debug($"Begin reading header from Master at IP: {EndPoint}");
int readBytes = await Stream.ReadAsync(_mbapHeader, 0, 6).ConfigureAwait(false);
if (readBytes == 0)
{
Logger.Debug($"0 bytes read, Master at {EndPoint} has closed Socket connection.");
_modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
return;
}
ushort frameLength = (ushort)IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_mbapHeader, 4));
Logger.Debug($"Master at {EndPoint} sent header: \"{string.Join(", ", _mbapHeader)}\" with {frameLength} bytes in PDU");
_messageFrame = new byte[frameLength];
readBytes = await Stream.ReadAsync(_messageFrame, 0, frameLength).ConfigureAwait(false);
if (readBytes == 0)
{
Logger.Debug($"0 bytes read, Master at {EndPoint} has closed Socket connection.");
_modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
return;
}
Logger.Debug($"Read frame from Master at {EndPoint} completed {readBytes} bytes");
byte[] frame = _mbapHeader.Concat(_messageFrame).ToArray();
Logger.Trace($"RX from Master at {EndPoint}: {string.Join(", ", frame)}");
var request = _modbusFactory.CreateModbusRequest(_messageFrame);
request.TransactionId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 0));
IModbusSlave slave = _slaveNetwork.GetSlave(request.SlaveAddress);
if (slave != null)
{
//TODO: Determine if this is appropriate
// perform action and build response
IModbusMessage response = slave.ApplyRequest(request);
response.TransactionId = request.TransactionId;
// write response
byte[] responseFrame = Transport.BuildMessageFrame(response);
Logger.Information($"TX to Master at {EndPoint}: {string.Join(", ", responseFrame)}");
await Stream.WriteAsync(responseFrame, 0, responseFrame.Length).ConfigureAwait(false);
}
}
}
// If an exception occurs (such as IOException in case of disconnect, or other failures), handle it as if the connection was gracefully closed
catch(Exception e)
{
Logger.Warning($"{e.GetType().Name} occured with Master at {EndPoint}. Closing connection.");
_modbusMasterTcpConnectionClosed?.Invoke(this, new TcpConnectionEventArgs(EndPoint));
return;
}
}
}
}