using System; using System.IO; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using XWorld.Framework.Protocol; namespace XWorld.Server.Gateway.Tests { // 集成测试用 WS 客户端:连接、发帧、带超时收一帧并解码 internal sealed class WsTestClient : IDisposable { private readonly ClientWebSocket _ws = new ClientWebSocket(); public async Task ConnectAsync(string wsBaseUrl, int pid) { await _ws.ConnectAsync(new Uri($"{wsBaseUrl}/ws?pid={pid}"), CancellationToken.None); } public async Task SendFrameAsync(Frame frame) { byte[] bytes = FrameCodec.Encode(frame); await _ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, CancellationToken.None); } // 在 timeout 内收一条 WS 消息并解码为 Frame;超时抛 TimeoutException public async Task ReceiveFrameAsync(int timeoutMs = 5000) { using var cts = new CancellationTokenSource(timeoutMs); var buf = new byte[8192]; using var ms = new MemoryStream(); WebSocketReceiveResult res; do { res = await _ws.ReceiveAsync(new ArraySegment(buf), cts.Token); if (res.MessageType == WebSocketMessageType.Close) throw new InvalidOperationException("server closed"); ms.Write(buf, 0, res.Count); } while (!res.EndOfMessage); return FrameCodec.Decode(ms.ToArray()); } public async Task CloseAsync() { try { await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); } catch { /* 忽略 */ } } public void Dispose() => _ws.Dispose(); } }