using System; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Threading; using log4net; using Modbus.IO; using Modbus.Message; using Modbus.Utility; namespace Modbus.Device { internal class ModbusMasterTcpConnection : ModbusDevice, IDisposable { private readonly ILog _log = LogManager.GetLogger(Assembly.GetCallingAssembly(), String.Format(CultureInfo.InvariantCulture, "{0}.Instance{1}", typeof(ModbusMasterTcpConnection).FullName, Interlocked.Increment(ref _instanceCounter))); private readonly TcpClient _client; private readonly string _endPoint; private readonly Stream _stream; private readonly ModbusTcpSlave _slave; private static int _instanceCounter; private byte[] _mbapHeader = new byte[6]; private byte[] _messageFrame; public ModbusMasterTcpConnection(TcpClient client, ModbusTcpSlave slave) : base(new ModbusIpTransport(new TcpClientAdapter(client))) { if (client == null) throw new ArgumentNullException("client"); if (slave == null) throw new ArgumentException("slave"); _client = client; _endPoint = client.Client.RemoteEndPoint.ToString(); _stream = client.GetStream(); _slave = slave; _log.DebugFormat("Creating new Master connection at IP:{0}", EndPoint); _log.Debug("Begin reading header."); Stream.BeginRead(_mbapHeader, 0, 6, ReadHeaderCompleted, null); } /// /// Occurs when a Modbus master TCP connection is closed. /// public event EventHandler ModbusMasterTcpConnectionClosed; public string EndPoint { get { return _endPoint; } } public Stream Stream { get { return _stream; } } public TcpClient TcpClient { get { return _client; } } internal void ReadHeaderCompleted(IAsyncResult ar) { _log.Debug("Read header completed."); CatchExceptionAndRemoveMasterEndPoint(() => { // this is the normal way a master closes its connection if (Stream.EndRead(ar) == 0) { _log.Debug("0 bytes read, Master has closed Socket connection."); EventHandler handler = ModbusMasterTcpConnectionClosed; if (handler != null) handler(this, new TcpConnectionEventArgs(EndPoint)); return; } _log.DebugFormat("MBAP header: {0}", StringUtility.Join(", ", _mbapHeader)); ushort frameLength = (ushort) IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_mbapHeader, 4)); _log.DebugFormat("{0} bytes in PDU.", frameLength); _messageFrame = new byte[frameLength]; Stream.BeginRead(_messageFrame, 0, frameLength, ReadFrameCompleted, null); }, EndPoint); } internal void ReadFrameCompleted(IAsyncResult ar) { CatchExceptionAndRemoveMasterEndPoint(() => { _log.DebugFormat("Read Frame completed {0} bytes", Stream.EndRead(ar)); byte[] frame = CollectionUtility.Concat(_mbapHeader, _messageFrame); _log.InfoFormat("RX: {0}", StringUtility.Join(", ", frame)); IModbusMessage request = ModbusMessageFactory.CreateModbusRequest(CollectionUtility.Slice(frame, 6, frame.Length - 6)); request.TransactionId = (ushort) IPAddress.NetworkToHostOrder(BitConverter.ToInt16(frame, 0)); // perform action and build response IModbusMessage response = _slave.ApplyRequest(request); response.TransactionId = request.TransactionId; // write response byte[] responseFrame = Transport.BuildMessageFrame(response); _log.InfoFormat("TX: {0}", StringUtility.Join(", ", responseFrame)); Stream.BeginWrite(responseFrame, 0, responseFrame.Length, WriteCompleted, null); }, EndPoint); } internal void WriteCompleted(IAsyncResult ar) { _log.Debug("End write."); CatchExceptionAndRemoveMasterEndPoint(() => { Stream.EndWrite(ar); _log.Debug("Begin reading another request."); Stream.BeginRead(_mbapHeader, 0, 6, ReadHeaderCompleted, null); }, EndPoint); } /// /// Catches all exceptions thrown when action is executed and removes the master end point. /// The exception is ignored when it simply signals a master closing its connection. /// internal void CatchExceptionAndRemoveMasterEndPoint(Action action, string endPoint) { if (action == null) throw new ArgumentNullException("action"); if (endPoint == null) throw new ArgumentNullException("endPoint"); if (String.IsNullOrEmpty(endPoint)) throw new ArgumentException("Argument endPoint cannot be empty."); try { action.Invoke(); } catch (IOException ioe) { _log.DebugFormat("IOException encountered in ReadHeaderCompleted - {0}", ioe.Message); EventHandler handler = ModbusMasterTcpConnectionClosed; if (handler != null) handler(this, new TcpConnectionEventArgs(EndPoint)); SocketException socketException = ioe.InnerException as SocketException; if (socketException != null && socketException.ErrorCode == Modbus.ConnectionResetByPeer) { _log.Debug("Socket Exception ConnectionResetByPeer, Master closed connection."); return; } throw; } catch (Exception e) { _log.Error("Unexpected exception encountered", e); throw; } } } }