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(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 } }