Skip to content

Commit

Permalink
reimplement UdpEchonetLiteHandler using with EchonetLiteHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
smdn committed Apr 4, 2024
1 parent c0b1958 commit 09ac8f4
Showing 1 changed file with 121 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#pragma warning disable CA2254 // CA2254: ログ メッセージ テンプレートは、LoggerExtensions.Log****(ILogger, string?, params object?[])' への呼び出しによって異なるべきではありません。 -->

using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
Expand All @@ -18,16 +21,23 @@

using Microsoft.Extensions.Logging;

namespace Smdn.Net.EchonetLite;
namespace Smdn.Net.EchonetLite.Transport;

public class UdpEchonetLiteHandler : IEchonetLiteHandler, IDisposable {
private readonly UdpClient receiveUdpClient;
public class UdpEchonetLiteHandler : EchonetLiteHandler, IDisposable {
private UdpClient? receiveUdpClient;
private readonly IReadOnlyList<IPAddress> selfAddresses;
private readonly ILogger logger;
private const int DefaultUdpPort = 3610;

/// <inheritdoc/>
public override IPAddress? LocalAddress => throw new NotSupportedException(); // TODO

/// <inheritdoc/>
public override ISynchronizeInvoke? SynchronizingObject { get; set; }

public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger)
{
var selfAddresses = NetworkInterface.GetAllNetworkInterfaces().SelectMany(ni => ni.GetIPProperties().UnicastAddresses.Select(ua => ua.Address));
selfAddresses = NetworkInterface.GetAllNetworkInterfaces().SelectMany(ni => ni.GetIPProperties().UnicastAddresses.Select(ua => ua.Address)).ToArray();

this.logger = logger;

Expand All @@ -37,75 +47,144 @@ public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger)
};
}
catch (Exception ex) {
this.logger.LogDebug(ex, "Exception");
this.logger.LogError(ex, $"unexpected exception occured while initialization");
throw;
}
}

Task.Run(async () => {
protected override void Dispose(bool disposing)
{
if (disposing) {
try {
while (true) {
var receivedResults = await receiveUdpClient.ReceiveAsync().ConfigureAwait(false);
if (selfAddresses.Contains(receivedResults.RemoteEndPoint.Address)) {
// ブロードキャストを自分で受信(無視)
continue;
}
this.logger.LogDebug($"UDP受信:{receivedResults.RemoteEndPoint.Address} {BitConverter.ToString(receivedResults.Buffer)}");
Received?.Invoke(this, (receivedResults.RemoteEndPoint.Address, receivedResults.Buffer.AsMemory()));
}
}
catch (System.ObjectDisposedException) {
// 握りつぶす
receiveUdpClient?.Close();
receiveUdpClient?.Dispose();
receiveUdpClient = null;
}
catch (Exception ex) {
this.logger.LogDebug(ex, "Exception");
logger.LogWarning(ex, $"unexpected exception occured while disposing {nameof(UdpClient)} for receiving");

// swallow all exceptions
}
});
}
}

public event EventHandler<(IPAddress, ReadOnlyMemory<byte>)>? Received;
base.Dispose(disposing);
}

public void Dispose()
protected override ValueTask DisposeAsyncCore()
{
logger.LogDebug("Dispose");

try {
receiveUdpClient?.Close();
receiveUdpClient?.Dispose();
receiveUdpClient = null;
}
catch (Exception ex) {
logger.LogDebug(ex, "Exception");
logger.LogWarning(ex, $"unexpected exception occured while disposing {nameof(UdpClient)} for receiving");

// swallow all exceptions
}

return base.DisposeAsyncCore();
}

public async ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
/// <inheritdoc/>
protected override async ValueTask<IPAddress> ReceiveAsyncCore(
IBufferWriter<byte> buffer,
CancellationToken cancellationToken
)
{
var remote = address is null
? new IPEndPoint(IPAddress.Broadcast, DefaultUdpPort)
: new IPEndPoint(address, DefaultUdpPort);
if (buffer is null)
throw new ArgumentNullException(nameof(buffer));
if (receiveUdpClient is null)
throw new ObjectDisposedException(GetType().FullName);

for (; ; ) {
var receivedResults = await receiveUdpClient
#if SYSTEM_NET_SOCKETS_UDPCLIENT_RECEIVEASYNC_CANCELLATIONTOKEN
.ReceiveAsync(cancellationToken)
#else
.ReceiveAsync()
#endif
.ConfigureAwait(false);

if (selfAddresses.Contains(receivedResults.RemoteEndPoint.Address))
// ブロードキャストを自分で受信した(無視)
continue;

logger.LogDebug($"UDP受信:{receivedResults.RemoteEndPoint.Address} {BitConverter.ToString(receivedResults.Buffer)}");

buffer.Write(receivedResults.Buffer);

return receivedResults.RemoteEndPoint.Address;
}
}

private void LogSend(IPEndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer)
{
#if SYSTEM_CONVERT_TOHEXSTRING
logger.LogDebug($"UDP送信:{remote.Address} {Convert.ToHexString(data.Span)}");
logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {Convert.ToHexString(buffer.Span)}");
#else
if (MemoryMarshal.TryGetArray(data, out var segment))
logger.LogDebug($"UDP送信:{remote.Address} {BitConverter.ToString(segment.Array!, segment.Offset, segment.Count)}");
if (MemoryMarshal.TryGetArray(buffer, out var segment))
logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {BitConverter.ToString(segment.Array!, segment.Offset, segment.Count)}");
else
logger.LogDebug($"UDP送信:{remote.Address} {BitConverter.ToString(data.ToArray())}");
logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {BitConverter.ToString(buffer.ToArray())}");
#endif
}

var sendUdpClient = new UdpClient() {
/// <summary>
/// Performs multicast send.
/// </summary>
/// <param name="buffer">The <see cref="ReadOnlyMemory{Byte}"/> in which the data to be sent is written.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
protected override async ValueTask SendAsyncCore(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken
)
{
var remoteEndPoint = new IPEndPoint(IPAddress.Broadcast, DefaultUdpPort);

LogSend(remoteEndPoint, buffer);

using var udpClient = new UdpClient() {
EnableBroadcast = true,
};

sendUdpClient.Connect(remote);
udpClient.Connect(remoteEndPoint);

#if SYSTEM_NET_SOCKETS_UDPCLIENT_SENDASYNC_READONLYMEMORY_OF_BYTE
await udpClient.SendAsync(buffer, cancellationToken).ConfigureAwait(false);
#else
await udpClient.SendAsync(buffer.ToArray(), buffer.Length).ConfigureAwait(false);
#endif

udpClient.Close();
}

/// <summary>
/// Performs unicast send to a specific remote address.
/// </summary>
/// <param name="remoteAddress">The <see cref="IPAddress"/> to which the data to be sent.</param>
/// <param name="buffer">The <see cref="ReadOnlyMemory{Byte}"/> in which the data to be sent is written.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
protected override async ValueTask SendToAsyncCore(
IPAddress remoteAddress,
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken
)
{
var remoteEndPoint = new IPEndPoint(remoteAddress, DefaultUdpPort);

LogSend(remoteEndPoint, buffer);

using var udpClient = new UdpClient();

udpClient.Connect(remoteEndPoint);

#if SYSTEM_NET_SOCKETS_UDPCLIENT_SENDASYNC_READONLYMEMORY_OF_BYTE
await sendUdpClient.SendAsync(data, cancellationToken).ConfigureAwait(false);
await udpClient.SendAsync(buffer, cancellationToken).ConfigureAwait(false);
#else
await sendUdpClient.SendAsync(data.ToArray(), data.Length).ConfigureAwait(false);
await udpClient.SendAsync(buffer.ToArray(), buffer.Length).ConfigureAwait(false);
#endif

sendUdpClient.Close();
udpClient.Close();
}
}

0 comments on commit 09ac8f4

Please sign in to comment.