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.
196 lines
7.0 KiB
196 lines
7.0 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.Udp;
|
|
|
|
public class UdpProtocol : CommunicationProtocol
|
|
{
|
|
public override Types.ProtocolType ProtocolType => Types.ProtocolType.Udp;
|
|
|
|
public override string InstanceIdentifier { get; protected set; } = string.Empty;
|
|
|
|
private INetworkProtocolSettings? settings;
|
|
private UdpClient? receivingUdpClient;
|
|
private UdpClient? sendingUdpClient;
|
|
|
|
private const int ReadTimeoutMs = 100; // milliseconds until the read operation timeouts
|
|
private const int WriteTimeoutMs = 100; // milliseconds until the write operation timeouts
|
|
|
|
public UdpProtocol(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(UdpProtocol)}.");
|
|
}
|
|
|
|
// store locally
|
|
this.settings = networkSettings;
|
|
|
|
// update identifier
|
|
this.InstanceIdentifier = NetworkProtocolHelpers.GetLimitedLengthHostname(this.settings);
|
|
|
|
// check if clients are null
|
|
if(this.receivingUdpClient != null || this.sendingUdpClient != null)
|
|
{
|
|
throw new Exception($"A UDP client was not null when {nameof(InternalConnect)} was called: " +
|
|
$"{nameof(receivingUdpClient)} isnull={this.receivingUdpClient == null}," +
|
|
$"{nameof(sendingUdpClient)} isnull={this.sendingUdpClient == null}");
|
|
}
|
|
|
|
/* create udp clients */
|
|
// try opening receiving udp socket
|
|
try
|
|
{
|
|
this.receivingUdpClient = new UdpClient(this.settings.Port);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Opening Receiving UDP socket failed:", nameof(UdpProtocol));
|
|
// rollback
|
|
this.InternalDisconnect();
|
|
return false;
|
|
}
|
|
|
|
|
|
// try opening sending udp socket
|
|
try
|
|
{
|
|
this.sendingUdpClient = new UdpClient();
|
|
this.sendingUdpClient.Connect(this.settings.Hostname!, this.settings.Port);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"'{nameof(InternalConnect)}()' Opening Sending UDP Socket failed:", nameof(UdpProtocol));
|
|
// rollback
|
|
this.InternalDisconnect();
|
|
return false;
|
|
}
|
|
|
|
// set static settings
|
|
this.receivingUdpClient.Client.ReceiveTimeout = ReadTimeoutMs;
|
|
this.sendingUdpClient.Client.SendTimeout = WriteTimeoutMs;
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override void InternalDisconnect()
|
|
{
|
|
// close receiving udp client
|
|
if(this.receivingUdpClient != null)
|
|
{
|
|
this.receivingUdpClient?.Close();
|
|
this.receivingUdpClient?.Dispose();
|
|
this.receivingUdpClient = null;
|
|
}
|
|
// close sending udp client
|
|
if (this.sendingUdpClient != null)
|
|
{
|
|
this.sendingUdpClient?.Close();
|
|
this.sendingUdpClient?.Dispose();
|
|
this.sendingUdpClient = null;
|
|
}
|
|
// reset settings
|
|
this.settings = null;
|
|
// send message that indicates that no protocol is connected anymore
|
|
this.messenger.Send(new UdpConnectedMessage(null));
|
|
}
|
|
|
|
protected override void InternalRead(CancellationToken ct)
|
|
{
|
|
while (ct.IsCancellationRequested == false)
|
|
{
|
|
// try receive message
|
|
IPEndPoint? remoteEndPoint = null;
|
|
byte[] receivedBytes = Array.Empty<byte>();
|
|
try
|
|
{
|
|
// will throw ObjectDisposedException if null
|
|
receivedBytes = this.receivingUdpClient!.Receive(ref remoteEndPoint);
|
|
}
|
|
catch (OperationCanceledException) // intentionally cancelled => just break
|
|
{
|
|
break;
|
|
}
|
|
catch (ObjectDisposedException objex)
|
|
{
|
|
this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalRead)}", nameof(UdpProtocol));
|
|
this.OnUnintentionallyDisconnected();
|
|
break; // break loop
|
|
}
|
|
catch (SocketException sockEx)
|
|
{
|
|
// timeout = normal use case
|
|
if (sockEx.SocketErrorCode != SocketError.TimedOut)
|
|
{
|
|
this.logger.LogException(sockEx, $"SocketException while reading data in {nameof(InternalRead)}", nameof(UdpProtocol));
|
|
this.messenger.Send<IUserInterfaceMessage>(new StoppedReadingUIMessage(this, sockEx.SocketErrorCode.ToString()));
|
|
break; // break loop
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(UdpProtocol));
|
|
this.messenger.Send<IUserInterfaceMessage>(new StoppedReadingUIMessage(this, ex.Message));
|
|
break; // break loop
|
|
}
|
|
|
|
// any data received?
|
|
if (receivedBytes.Length > 0)
|
|
{
|
|
foreach (byte b in receivedBytes)
|
|
{
|
|
// report received data
|
|
this.OnReceivedData(new ExtendedByte(b));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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(UdpProtocol));
|
|
return false;
|
|
}
|
|
|
|
// try sending the data
|
|
try
|
|
{
|
|
// will throw ObjectDisposedException if null
|
|
this.sendingUdpClient!.Send(bytes, bytes.Length);
|
|
}
|
|
catch (ObjectDisposedException objex)
|
|
{
|
|
this.logger.LogException(objex, $"ObjectDisposedException while sending data in {nameof(InternalSendBytes)}", nameof(UdpProtocol));
|
|
this.OnUnintentionallyDisconnected();
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
this.logger.LogException(ex, $"Exception while sending data in {nameof(InternalSendBytes)}", nameof(UdpProtocol));
|
|
return false;
|
|
}
|
|
|
|
foreach (byte b in bytes)
|
|
{
|
|
// report sent data
|
|
this.OnSentData(new ExtendedByte(b));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|