453 lines
16 KiB
C#
453 lines
16 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// returns true if the unit is soft disconnected
|
|
/// </summary>
|
|
public bool IsSoftDisconnected { get; private set; } = false;
|
|
/// <summary>
|
|
/// soft disconnects the unit, kills the socket and disposes it
|
|
/// </summary>
|
|
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;
|
|
/// <summary>
|
|
/// soft connects the unit
|
|
/// </summary>
|
|
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!
|
|
}
|
|
|
|
/// <summary>
|
|
/// the host ip address, sent during original connect and preserved for future connect/disconnect
|
|
/// </summary>
|
|
private string _hostIPAddress;
|
|
|
|
/// <summary>
|
|
/// creates and setups up a socket, returns the socket created
|
|
/// </summary>
|
|
/// <param name="connectString"></param>
|
|
/// <param name="hostIPAddress"></param>
|
|
/// <returns></returns>
|
|
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<int> SendAsync(byte[] sendBuffer, int bufferStartOffset, int bufferSizeToSend)
|
|
{
|
|
return Task<int>.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
|
|
}
|
|
}
|