|
|
|
@ -0,0 +1,188 @@ |
|
|
|
|
|
|
|
using Common.Logging; |
|
|
|
|
|
|
|
using Common.Messaging; |
|
|
|
|
|
|
|
using CommunityToolkit.Mvvm.Messaging; |
|
|
|
|
|
|
|
using MultiTerm.Protocols.Helpers; |
|
|
|
|
|
|
|
using MultiTerm.Protocols.Model; |
|
|
|
|
|
|
|
using MultiTerm.Protocols.Network; |
|
|
|
|
|
|
|
using MultiTerm.Protocols.Types; |
|
|
|
|
|
|
|
using System.Net; |
|
|
|
|
|
|
|
using System.Net.Sockets; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace MultiTerm.Protocols.Tcp; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class TcpClientProtocol : CommunicationProtocol |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
public override Types.ProtocolType ProtocolType => Types.ProtocolType.Tcp_Client; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override string InstanceIdentifier { get; protected set; } = string.Empty; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private INetworkProtocolSettings? settings; |
|
|
|
|
|
|
|
private TcpClient? client; |
|
|
|
|
|
|
|
private NetworkStream? stream; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const int BufferSizeBytes = 1024; // number of bytes when reading data from TCP server |
|
|
|
|
|
|
|
private const int ReadTimeoutMs = 100; // milliseconds until the read operation timeouts |
|
|
|
|
|
|
|
private const int WriteTimeoutMs = 100; // milliseconds until the write operation timeouts |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public TcpClientProtocol(ILogger logger, IMessenger messenger) : base(logger, messenger) { } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override bool InternalConnect(IProtocolSettings settings) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// check if settings are of correct type |
|
|
|
|
|
|
|
if (settings is not INetworkProtocolSettings networkSettings) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.settings = null; |
|
|
|
|
|
|
|
throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " + |
|
|
|
|
|
|
|
$"Check parameter {nameof(settings)}' of '{nameof(InternalConnect)}()' in {nameof(TcpClientProtocol)}."); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// store locally |
|
|
|
|
|
|
|
this.settings = networkSettings; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// update identifier |
|
|
|
|
|
|
|
this.InstanceIdentifier = NetworkProtocolHelpers.GetLimitedLengthHostname(this.settings); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if client is null |
|
|
|
|
|
|
|
if (this.client != null) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
throw new Exception($"The TCP client was not null when {nameof(InternalConnect)} was called."); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///* resolve hostname */ |
|
|
|
|
|
|
|
//IPAddress ipAddress; |
|
|
|
|
|
|
|
//try |
|
|
|
|
|
|
|
//{ |
|
|
|
|
|
|
|
// IPHostEntry ipHostInfo = Dns.GetHostEntry(this.settings.Hostname); |
|
|
|
|
|
|
|
// ipAddress = ipHostInfo.AddressList[0]; |
|
|
|
|
|
|
|
//} |
|
|
|
|
|
|
|
//catch (Exception ex) |
|
|
|
|
|
|
|
//{ |
|
|
|
|
|
|
|
// this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Failed to resolve hostname '{this.settings.Hostname}'.", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
// return false; |
|
|
|
|
|
|
|
//} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* create client */ |
|
|
|
|
|
|
|
//var ipEndPoint = new IPEndPoint(ipAddress, this.settings.Port); |
|
|
|
|
|
|
|
this.client = new(AddressFamily.Unknown); // creates dual stack tcp client (ipv4 and ipv6) |
|
|
|
|
|
|
|
try |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.client.Connect(this.settings.Hostname, this.settings.Port); |
|
|
|
|
|
|
|
this.stream = client.GetStream(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception ex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Creating TCP client failed:", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
// rollback |
|
|
|
|
|
|
|
this.InternalDisconnect(); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// set static settings |
|
|
|
|
|
|
|
this.stream.ReadTimeout = ReadTimeoutMs; |
|
|
|
|
|
|
|
this.stream.WriteTimeout = WriteTimeoutMs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// send message to inform about resolved IP address |
|
|
|
|
|
|
|
if (this.client.Client.RemoteEndPoint is IPEndPoint remoteEndpoint) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.messenger.Send(new TcpConnectedMessage(remoteEndpoint.Address)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override void InternalDisconnect() |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// close client |
|
|
|
|
|
|
|
if (this.client != null) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.client?.Close(); |
|
|
|
|
|
|
|
this.client?.Dispose(); |
|
|
|
|
|
|
|
this.client = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// close stream |
|
|
|
|
|
|
|
if (this.stream != null) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.stream?.Close(); |
|
|
|
|
|
|
|
this.stream?.Dispose(); |
|
|
|
|
|
|
|
this.stream = null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// reset settings |
|
|
|
|
|
|
|
this.settings = null; |
|
|
|
|
|
|
|
// send message that indicates that no protocol is connected anymore |
|
|
|
|
|
|
|
this.messenger.Send(new TcpConnectedMessage(null)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override void InternalRead(CancellationToken ct) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
while (ct.IsCancellationRequested == false) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// try receive message with a buffer |
|
|
|
|
|
|
|
int readByte = -1; |
|
|
|
|
|
|
|
try |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// will throw ObjectDisposedException if null |
|
|
|
|
|
|
|
// timeout is defined after creation of stream in ctor |
|
|
|
|
|
|
|
readByte = this.stream!.ReadByte(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (OperationCanceledException) // intentionally cancelled => just break |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (ObjectDisposedException objex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalRead)}", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
this.OnUnintentionallyDisconnected(); |
|
|
|
|
|
|
|
break; // break loop |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception ex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
this.messenger.Send<IUserInterfaceMessage>(new StoppedReadingUIMessage(this, ex.Message)); |
|
|
|
|
|
|
|
break; // break loop |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// any data received? |
|
|
|
|
|
|
|
if(readByte != -1) // -1 = end of stream or default value |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// report received byte |
|
|
|
|
|
|
|
this.OnReceivedData(new ExtendedByte((byte)readByte)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected override bool InternalSendBytes(byte[] bytes) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// check for empty bytes array |
|
|
|
|
|
|
|
if (bytes == null || bytes.Length == 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogWarn($"'{nameof(InternalSendBytes)}()' got null or empty bytes array.", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try sending each byte individually |
|
|
|
|
|
|
|
foreach (byte b in bytes) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
try |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// will throw ObjectDisposedException if null |
|
|
|
|
|
|
|
this.stream!.WriteByte(b); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (ObjectDisposedException objex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalSendBytes)}", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
this.OnUnintentionallyDisconnected(); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception ex) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
this.logger.LogException(ex, $"Exception while sending data in {nameof(InternalSendBytes)}", nameof(TcpClientProtocol)); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// report sent data |
|
|
|
|
|
|
|
this.OnSentData(new ExtendedByte(b)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; // success |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |