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