Files
lserp_cs_6.0/插件库/Lskj.AgentBridge/DeadlineFrameCodec.cs
T
2026-08-14 14:28:28 +08:00

188 lines
6.3 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace Lskj.AgentBridge
{
/// <summary>
/// Length-prefixed bridge framing with one total deadline for the header
/// and body. A stalled asynchronous operation closes the owning stream so
/// a local client cannot retain a server worker indefinitely.
/// </summary>
internal static class DeadlineFrameCodec
{
internal const int MaximumMessageBytes = 1024 * 1024;
internal static byte[] ReadFrame(Stream stream, TimeSpan timeout)
{
if (stream == null) throw new ArgumentNullException("stream");
ValidateTimeout(timeout);
Stopwatch stopwatch = Stopwatch.StartNew();
byte[] header = ReadExactly(stream, 4, timeout, stopwatch);
int length = header[0]
| (header[1] << 8)
| (header[2] << 16)
| (header[3] << 24);
if (length <= 0 || length > MaximumMessageBytes)
throw new InvalidDataException("请求长度无效或超过 1 MB 限制。");
return ReadExactly(stream, length, timeout, stopwatch);
}
internal static void WriteFrame(Stream stream, byte[] body, TimeSpan timeout)
{
if (stream == null) throw new ArgumentNullException("stream");
ValidateTimeout(timeout);
if (body == null || body.Length == 0 || body.Length > MaximumMessageBytes)
throw new InvalidDataException("响应长度无效或超过 1 MB 限制。");
Stopwatch stopwatch = Stopwatch.StartNew();
int length = body.Length;
byte[] header = new[]
{
(byte)(length & 0xff),
(byte)((length >> 8) & 0xff),
(byte)((length >> 16) & 0xff),
(byte)((length >> 24) & 0xff)
};
WriteWithDeadline(stream, header, 0, header.Length, timeout, stopwatch);
WriteWithDeadline(stream, body, 0, body.Length, timeout, stopwatch);
stream.Flush();
}
private static byte[] ReadExactly(
Stream stream,
int length,
TimeSpan timeout,
Stopwatch stopwatch)
{
byte[] buffer = new byte[length];
int offset = 0;
while (offset < length)
{
int read = ReadWithDeadline(
stream,
buffer,
offset,
length - offset,
timeout,
stopwatch);
if (read <= 0)
throw new EndOfStreamException("客户端在完整消息到达前断开连接。");
offset += read;
}
return buffer;
}
private static int ReadWithDeadline(
Stream stream,
byte[] buffer,
int offset,
int count,
TimeSpan timeout,
Stopwatch stopwatch)
{
RemainingMilliseconds(timeout, stopwatch);
IAsyncResult pending = stream.BeginRead(buffer, offset, count, null, null);
Func<int> complete = delegate { return stream.EndRead(pending); };
int remaining;
try
{
remaining = RemainingMilliseconds(timeout, stopwatch);
}
catch (TimeoutException)
{
AbortPending(stream, pending, complete);
throw;
}
return CompleteWithDeadline(stream, pending, remaining, complete);
}
private static void WriteWithDeadline(
Stream stream,
byte[] buffer,
int offset,
int count,
TimeSpan timeout,
Stopwatch stopwatch)
{
RemainingMilliseconds(timeout, stopwatch);
IAsyncResult pending = stream.BeginWrite(buffer, offset, count, null, null);
Func<int> complete = delegate
{
stream.EndWrite(pending);
return 0;
};
int remaining;
try
{
remaining = RemainingMilliseconds(timeout, stopwatch);
}
catch (TimeoutException)
{
AbortPending(stream, pending, complete);
throw;
}
CompleteWithDeadline(stream, pending, remaining, complete);
}
private static int CompleteWithDeadline(
Stream stream,
IAsyncResult pending,
int remaining,
Func<int> complete)
{
WaitHandle waitHandle = pending.AsyncWaitHandle;
if (!waitHandle.WaitOne(remaining, false))
{
AbortPending(stream, waitHandle, complete);
throw new TimeoutException("命名管道帧传输超时。");
}
try
{
return complete();
}
finally
{
waitHandle.Close();
}
}
private static void AbortPending(
Stream stream,
IAsyncResult pending,
Func<int> complete)
{
AbortPending(stream, pending.AsyncWaitHandle, complete);
}
private static void AbortPending(
Stream stream,
WaitHandle waitHandle,
Func<int> complete)
{
try { stream.Dispose(); }
finally
{
try { complete(); }
catch { }
waitHandle.Close();
}
}
private static int RemainingMilliseconds(TimeSpan timeout, Stopwatch stopwatch)
{
double value = (timeout - stopwatch.Elapsed).TotalMilliseconds;
if (value <= 0) throw new TimeoutException("命名管道帧传输超时。");
return Math.Max(1, (int)Math.Min(int.MaxValue, Math.Ceiling(value)));
}
private static void ValidateTimeout(TimeSpan timeout)
{
if (timeout <= TimeSpan.Zero || timeout > TimeSpan.FromMinutes(1))
throw new ArgumentOutOfRangeException(
"timeout",
"命名管道帧超时必须在 1 分钟以内。");
}
}
}