Files
AIC-Project/Server/Gateway/DevDiscoveryResponder.cs
2026-07-10 10:24:29 +08:00

83 lines
3.0 KiB
C#

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using XWorld.Framework.Protocol;
namespace XWorld.Server.Gateway
{
public sealed class DevDiscoveryResponder
{
private readonly DevDiscoveryReply _template;
private readonly string _token;
private readonly UdpClient _udp;
private CancellationTokenSource _cts;
private Task _loop;
public int Port { get; }
public DevDiscoveryResponder(string serverName, string gatewayUrl, string resourceBaseUrl,
string token, int listenPort = DevDiscovery.Port)
{
if (string.IsNullOrEmpty(token)) throw new ArgumentException("token cannot be empty", nameof(token));
_token = token;
_template = new DevDiscoveryReply
{
ProtoVersion = DevDiscovery.ProtoVersion,
ServerName = serverName ?? "dev-server",
GatewayUrl = gatewayUrl ?? "",
ResourceBaseUrl = resourceBaseUrl ?? "",
Token = token,
};
_udp = new UdpClient(AddressFamily.InterNetwork);
_udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udp.Client.Bind(new IPEndPoint(IPAddress.Any, listenPort));
Port = ((IPEndPoint)_udp.Client.LocalEndPoint).Port;
}
public void Start()
{
if (_loop != null) return;
_cts = new CancellationTokenSource();
_loop = ReceiveLoopAsync(_cts.Token);
}
private async Task ReceiveLoopAsync(CancellationToken ct)
{
using (ct.Register(() => { try { _udp.Close(); } catch { } }))
{
while (!ct.IsCancellationRequested)
{
UdpReceiveResult res;
try { res = await _udp.ReceiveAsync().ConfigureAwait(false); }
catch (ObjectDisposedException) { break; }
catch (SocketException) { break; }
if (!DevDiscoveryCodec.TryDecodeQuery(res.Buffer, out var q)) continue;
if (q.ProtoVersion != DevDiscovery.ProtoVersion) continue;
if (!string.Equals(q.Token, _token, StringComparison.Ordinal)) continue;
DevDiscoveryReply reply = _template;
reply.Nonce = q.Nonce;
byte[] bytes = DevDiscoveryCodec.EncodeReply(reply);
try { await _udp.SendAsync(bytes, bytes.Length, res.RemoteEndPoint).ConfigureAwait(false); }
catch (ObjectDisposedException) { break; }
catch (SocketException) { }
}
}
}
public async Task StopAsync()
{
if (_cts == null) return;
_cts.Cancel();
try { await _loop.ConfigureAwait(false); }
catch { }
_cts.Dispose();
_cts = null;
_loop = null;
}
}
}