using System.Buffers.Binary; using System.Net.WebSockets; using Fleet.Rpc; namespace Fleet.Link; /// WebSocket 传输(节点 → 服务端):文本帧是 JSON-RPC,二进制帧是发件箱。 public sealed class WsTransport : IRpcTransport { private readonly WebSocket _socket; private readonly int _maxFrameBytes; private readonly SemaphoreSlim _sendLock = new(1, 1); public WsTransport(WebSocket socket, int maxFrameBytes) { _socket = socket; _maxFrameBytes = maxFrameBytes; } public async ValueTask SendAsync(RpcFrame frame, CancellationToken ct) { await _sendLock.WaitAsync(ct); try { await _socket.SendAsync(frame.Data, frame.Kind == FrameKind.Text ? WebSocketMessageType.Text : WebSocketMessageType.Binary, endOfMessage: true, ct); } finally { _sendLock.Release(); } } public async ValueTask ReceiveAsync(CancellationToken ct) { var message = new MemoryStream(); byte[] buffer = new byte[16 * 1024]; while (true) { ValueWebSocketReceiveResult result = await _socket.ReceiveAsync(buffer.AsMemory(), ct); if (result.MessageType == WebSocketMessageType.Close) { if (_socket.State == WebSocketState.CloseReceived) { // 按协议确认对端发起的关闭 try { await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "ack", CancellationToken.None); } catch (WebSocketException) { // 对端已经断开 } } return null; } message.Write(buffer, 0, result.Count); if (message.Length > _maxFrameBytes) { await CloseAsync("frame too large", CancellationToken.None); return null; } if (result.EndOfMessage) { return new RpcFrame(result.MessageType == WebSocketMessageType.Text ? FrameKind.Text : FrameKind.Binary, message.ToArray()); } } } public async ValueTask CloseAsync(string reason, CancellationToken ct) { if (_socket.State is WebSocketState.Open or WebSocketState.CloseReceived) { try { await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, reason, ct); } catch (WebSocketException) { // 对端已经断开 } } } public ValueTask DisposeAsync() { _socket.Dispose(); _sendLock.Dispose(); return ValueTask.CompletedTask; } } /// /// TLS 帧传输(Probe → Agent):u32 LE 正文长度 | u8 类型(0 文本, 1 二进制) | 正文。 /// 比 WebSocket 轻得多,Agent 不需要内置 HTTP 服务端。 /// public sealed class TlsFrameTransport : IRpcTransport { private const int HeaderBytes = 5; private readonly Stream _stream; private readonly int _maxFrameBytes; private readonly SemaphoreSlim _sendLock = new(1, 1); public TlsFrameTransport(Stream stream, int maxFrameBytes) { _stream = stream; _maxFrameBytes = maxFrameBytes; } public async ValueTask SendAsync(RpcFrame frame, CancellationToken ct) { if (frame.Data.Length > _maxFrameBytes) { throw new InvalidOperationException("frame exceeds the maximum size"); } byte[] header = new byte[HeaderBytes]; BinaryPrimitives.WriteUInt32LittleEndian(header, (uint)frame.Data.Length); header[4] = (byte)frame.Kind; await _sendLock.WaitAsync(ct); try { await _stream.WriteAsync(header, ct); await _stream.WriteAsync(frame.Data, ct); await _stream.FlushAsync(ct); } finally { _sendLock.Release(); } } public async ValueTask ReceiveAsync(CancellationToken ct) { byte[] header = new byte[HeaderBytes]; try { if (!await ReadExactlyOrEofAsync(header, ct)) { return null; } uint length = BinaryPrimitives.ReadUInt32LittleEndian(header); if (length > _maxFrameBytes || header[4] > (byte)FrameKind.Binary) { await _stream.DisposeAsync(); // 非法帧:直接断开 return null; } byte[] body = new byte[length]; if (!await ReadExactlyOrEofAsync(body, ct)) { return null; } return new RpcFrame((FrameKind)header[4], body); } catch (IOException) { return null; } catch (ObjectDisposedException) { return null; } } public async ValueTask CloseAsync(string reason, CancellationToken ct) { try { await _stream.FlushAsync(ct); } catch (Exception ex) when (ex is IOException or ObjectDisposedException) { // 对端已经断开 } await _stream.DisposeAsync(); } public async ValueTask DisposeAsync() { await _stream.DisposeAsync(); _sendLock.Dispose(); } private async ValueTask ReadExactlyOrEofAsync(byte[] buffer, CancellationToken ct) { int read = 0; while (read < buffer.Length) { int n = await _stream.ReadAsync(buffer.AsMemory(read), ct); if (n == 0) { return false; } read += n; } return true; } }