Multiprocotol Terminalprogram (BAT)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MultiTerm/MultiTerm.Protocols/Tcp/TcpClientProtocol.cs

189 lines
6.9 KiB

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;
public override string LongInstanceIdentifier { 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);
this.LongInstanceIdentifier = this.settings.Hostname;
// check if client is null
if (this.client != null)
{
throw new Exception($"The TCP client was not null when {nameof(InternalConnect)} was called.");
}
/* 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 (IOException ioEx)
{
// socket exception and timeout => normal use case
if (ioEx.InnerException is SocketException sockEx && sockEx?.SocketErrorCode == SocketError.TimedOut) { }
// other IO Exception
else
{
this.logger.LogException(ioEx, $"IOException while reading data in {nameof(InternalRead)}", nameof(TcpClientProtocol));
this.messenger.Send<IUserInterfaceMessage>(new StoppedReadingUIMessage(this, ioEx.ToString()));
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
}
}