188 lines
6.3 KiB
C#
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 分钟以内。");
|
|
}
|
|
}
|
|
}
|