feat: add ERP agent pet bridge and startup guide
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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 分钟以内。");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user