using System; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using DTS.Common.DASResource; using DTS.Common.Enums.DASFactory; using DTS.Common.Interface.Connection; using DTS.Common.Utilities.Logging; namespace DTS.Common { public class EthernetConnection : IConnection { /// /// returns true if the unit is soft disconnected /// public bool IsSoftDisconnected { get; private set; } = false; /// /// soft disconnects the unit, kills the socket and disposes it /// public void SoftDisconnect() { if (!Enums.Hardware.HardwareConstants.AllowSoftDisconnects) { return; } if (Sock.Connected) { Sock.Disconnect(false); Sock.Dispose(); IsSoftDisconnected = true; APILogger.Log($"SoftDisconnect {ConnectString}"); System.Threading.Thread.Sleep(50); } else { APILogger.Log($"SoftDisconnect denied {ConnectString}"); } } public bool RequiresKeepAliveReset { get; set; } = false; /// /// soft connects the unit /// public void SoftConnect() { if (!Enums.Hardware.HardwareConstants.AllowSoftDisconnects) { return; } if (Sock.Connected) { APILogger.Log($"SoftConnect denied {ConnectString}"); return; } var parameters = Connect_String.Split(':'); //create and setup socket Create(Connect_String, _hostIPAddress); if (RequiresKeepAliveReset) { APILogger.Log("SoftConnect connecting to HB"); //before we connect to the socket we must talk to the command port and setup //keep alive var sock2 = CreateSock(ConnectString, _hostIPAddress); sock2.Connect(parameters[0], 8200); var msg = "<60,5,4>"; sock2.Send(System.Text.Encoding.ASCII.GetBytes(msg)); System.Threading.Thread.Sleep(100); var bytes = new byte[1024]; sock2.Receive(bytes); sock2.Disconnect(false); sock2.Dispose(); //we're done with the command port, go connect to the actual device port } int attempt = 0; while (attempt < 3) { attempt++; try { Sock.Connect(parameters[0], Convert.ToInt32(parameters[1])); attempt = 4;//break out } catch (SocketException se) { APILogger.Log(se); System.Threading.Thread.Sleep(1000); } } IsSoftDisconnected = false; APILogger.Log($"Soft connect {parameters[0]}:{parameters[1]}"); } void IConnection.KeepAliveErrorReceived() { if (Sock != null) { if (Sock.Connected) { Sock.Shutdown(SocketShutdown.Both); Sock.Close(); } Sock.Dispose(); Sock = null; } OnDisconnected?.Invoke(this, new EventArgs()); } public string GetConnectionData() { try { return null == Sock ? "" : $"local: {Sock.LocalEndPoint}, Remote: {Sock.RemoteEndPoint}"; } catch (Exception) { } return ""; } public Socket Sock; protected string Connect_String; private bool _disposed; public event EventHandler OnDisconnected; public bool Connected => Sock != null && Sock.Connected; //public bool connected; public string ConnectString => Connect_String; public SocketFlags Flags { get; set; } public EthernetConnection() { _disposed = false; } protected EthernetConnection(Socket sock) { _disposed = false; Sock = sock; } ~EthernetConnection() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed){ return; } if (disposing) { try { if (Sock != null) { APILogger.Log("sock.Disconnect() - during dispose", ConnectString); if (Sock.Connected) { Sock.Shutdown(SocketShutdown.Both); Sock.Close(); } Sock.Dispose(); } Sock = null; } catch (Exception ex) { APILogger.Log(ex); } } _disposed = true; } public void Create(string connectString) { //Create(ConnectString); this is recursive! } /// /// the host ip address, sent during original connect and preserved for future connect/disconnect /// private string _hostIPAddress; /// /// creates and setups up a socket, returns the socket created /// /// /// /// public Socket CreateSock(string connectString, string hostIPAddress) { var sock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); sock2.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); sock2.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); sock2.SendBufferSize = DFConstantsAndEnums.SendBufferSizeBytes; sock2.ReceiveBufferSize = DFConstantsAndEnums.ReceiveBufferSizeBytes; // https://benohead.com/windows-network-connections-timing-quickly-temporary-connectivity-loss/ //KeepAliveTime: default value is 2hr //KeepAliveInterval: default value is 1s and Detect 5 times const uint dummy = 0; //lenth = 4 var inOptionValues = new byte[System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 3]; BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0); // Keepalive On true BitConverter.GetBytes(DFConstantsAndEnums.LocalKeepAliveTimeOutMS) .CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy)); BitConverter.GetBytes(DFConstantsAndEnums.LocalKeepAliveRetryIntervalMS) .CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 2); sock2.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); if (!string.IsNullOrEmpty(hostIPAddress)) { APILogger.Log($"Binding to {hostIPAddress}"); sock2.Bind(new IPEndPoint(IPAddress.Parse(hostIPAddress), 0)); } return sock2; } public void Create(string connectString, string hostIPAddress) { _hostIPAddress = hostIPAddress; Sock = CreateSock(connectString, hostIPAddress); Connect_String = connectString; } #region Connect public IAsyncResult BeginConnect(AsyncCallback cb, object state) { if (Sock == null) { // "EthernetConnection.BeginConnect: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_BeginConnect_Err3); } if (string.IsNullOrEmpty(Connect_String)) { // "EthernetConnection.BeginConnect: Connect_String is empty" throw new Exception(DASResource.Strings.EthernetConnection_BeginConnect_Err1); } var parameters = Connect_String.Split(':'); if (parameters.Length != 2) { // "EthernetConnection.BeginConnect: Connect_String is invalid" throw new Exception(DASResource.Strings.EthernetConnection_BeginConnect_Err2); } if (!int.TryParse(parameters[1], out var portNum)) { // "EthernetConnection.BeginConnect: Connect_String has invalid port number" throw new Exception(DASResource.Strings.EthernetConnection_BeginConnect_Err4); } return Sock.BeginConnect(parameters[0], portNum, cb, state); } public void EndConnect(IAsyncResult ar) { if (Sock == null) { APILogger.Log("EthernetConnection::EndConnect socket is not created"); // "EthernetConnection.EndConnect: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_EndConnect_Err1); } APILogger.Log("Successfully connected local: " + Sock.LocalEndPoint + " remote: " + Sock.RemoteEndPoint); Sock.EndConnect(ar); } #endregion #region Disconnect public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback cb, object state) { APILogger.Log("BeginDisconnect", ConnectString); if (Sock == null) { // "EthernetConnection.BeginDisconnect: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_BeginDisconnect_Err1); } //if know the socket is not connected there's no point in trying to disconnect //however we need to let the caller know that they shouldn't wait for the //EndDisconnect if (!Sock.Connected) { throw new SocketException(DFConstantsAndEnums.WSAEISCONN); } return Sock.BeginDisconnect(reuseSocket, cb, state); } public void EndDisconnect(IAsyncResult asyncResult) { APILogger.Log("EndDisconnect()", ConnectString); if (Sock == null) { // "EthernetConnection.EndDisconnect: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_EndDisconnect_Err1); } Sock.EndDisconnect(asyncResult); } #endregion #region Accept public IAsyncResult BeginAccept(AsyncCallback callback, object state) { if (Sock == null) { // "EthernetConnection.BeginAccept: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_BeginAccept_Err1); } return Sock.BeginAccept(callback, state); } public IConnection EndAccept(IAsyncResult asyncResult) { if (Sock == null) { // "EthernetConnection.EndAccept: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_EndAccept_Err1); } var newSock = Sock.EndAccept(asyncResult); var newConnection = new EthernetConnection(newSock); return newConnection; } #endregion #region Bind public void Bind(int port) { if (Sock == null) { // "EthernetConnection.Bind: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_Bind_Err1); } var ipHostInfo = Dns.GetHostEntry(Dns.GetHostName()); var ipAddress = ipHostInfo.AddressList[0]; var localEndPoint = new IPEndPoint(ipAddress, port); APILogger.Log($"EthernetConnection.Bind, binding to {localEndPoint.Address} - {ipHostInfo.HostName}"); Sock.Bind(localEndPoint); } #endregion #region Listen public void Listen(int backlog) { if (Sock == null) { // "EthernetConnection.Listen: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_Listen_Err1); } Sock.Listen(backlog); } #endregion #region Send public IAsyncResult BeginSend(byte[] buffer, int offset, int size, AsyncCallback cb, object state) { if (Sock == null) { // "EthernetConnection.BeginSend: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_BeginSend_Err1); } if (cb == null) { // "EthernetConnection.BeginSend: callback can't be null" throw new Exception(DASResource.Strings.EthernetConnection_BeginSend_Err2); } //FB 18389 if the connection is not open do not begin send process if (!Sock.Connected) { // "EthernetConnection.BeginSend: socket is not connected." throw new Exception(DASResource.Strings.EthernetConnection_BeginSend_Err3); } return Sock.BeginSend(buffer, offset, size, Flags, cb, state); } public int EndSend(IAsyncResult ar) { if (Sock == null) { // "EthernetConnection.EndSend: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_EndSend_Err1); } try { var ret = Sock.EndSend(ar); return ret; } catch (Exception) { Sock = null; throw; } } public Task SendAsync(byte[] sendBuffer, int bufferStartOffset, int bufferSizeToSend) { return Task.Factory.FromAsync( (callback, state) => BeginSend(sendBuffer, bufferStartOffset, bufferSizeToSend, callback, state), EndSend, state: null ); } #endregion #region Receive public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, AsyncCallback cb, object state) { if (Sock == null) { // "EthernetConnection.BeginReceive: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_BeginReceive_Err1); } if (cb == null) { // "EthernetConnection.BeginReceive: callback can't be null" throw new Exception(DASResource.Strings.EthernetConnection_BeginReceive_Err2); } return Sock.BeginReceive(buffer, offset, size, Flags, cb, state); } public int EndReceive(IAsyncResult ar) { if (Sock == null) { // "EthernetConnection.EndReceive: socket is not created" throw new Exception(DASResource.Strings.EthernetConnection_EndReceive_Err1); } return Sock.EndReceive(ar); } #endregion } }