Я подозреваю, что это тонкое состояние гонки, связанное с TcpClient или PipeReader/
Код: Выделить всё
PipeWriterЦель:
Сервер должен принимать соединение, обрабатывать начальный пакет запросов и поддерживать соединение для последующего обмена данными. Клиент должен получать все ответы без каких-либо ошибок соединения.
Код:
Основная логика находится в моем классе ScaffoldingServer. Новое клиентское соединение обрабатывается _HandleClientAsync, который называется задачей «запустил и забыл» (
Код: Выделить всё
_ = _HandleClientAsync(tcpClient, ct);Вот соответствующие методы:
Код: Выделить всё
_HandleClientAsyncКод: Выделить всё
private async Task _HandleClientAsync(TcpClient tcpClient, CancellationToken ct)
{
var sessionId = Guid.NewGuid().ToString();
// Log: "New client connected..."
using (tcpClient)
{
var stream = tcpClient.GetStream();
var reader = PipeReader.Create(stream);
var writer = PipeWriter.Create(stream);
try
{
while (true)
{
var readResult = await reader.ReadAsync(ct).ConfigureAwait(false);
var buffer = readResult.Buffer;
while (TryParseFrame(ref buffer, out var requestFrame))
{
// Log: "Received complete frame..."
// Business logic to handle the request and get a responseBody...
var responseHeader = new byte[5];
// ... (populate responseHeader)
await writer.WriteAsync(responseHeader, ct).ConfigureAwait(false);
if (responseBody.Length > 0)
{
await writer.WriteAsync(responseBody, ct).ConfigureAwait(false);
}
var flushResult = await writer.FlushAsync(ct).ConfigureAwait(false);
// Log: "Response sent..."
if (flushResult.IsCanceled || flushResult.IsCompleted)
{
goto ConnectionClosed;
}
}
reader.AdvanceTo(buffer.Start, buffer.End);
if (readResult.IsCompleted)
{
// Log: "Client has closed the sending channel. Finishing up."
break;
}
}
}
catch (Exception ex) { /* Log error */ }
ConnectionClosed:
// Attempted fix: Wait a bit to ensure packets are sent before closing. This did not reliably work.
// await Task.Delay(200, ct);
// Log: "Player with session ... disconnected."
} // The using statement disposes tcpClient here
}
Код: Выделить всё
TryParseFrameКод: Выделить всё
private bool TryParseFrame(ref ReadOnlySequence buffer, out (string TypeInfo, byte[] Body, SequencePosition End) frame)
{
frame = default;
if (buffer.Length < 1) return false;
byte typeLength = buffer.FirstSpan[0];
long headerLength = 1 + typeLength + 4;
if (buffer.Length < headerLength) return false;
var headerSlice = buffer.Slice(0, headerLength);
ReadOnlySpan headerSpan;
if (headerSlice.IsSingleSegment)
{
headerSpan = headerSlice.FirstSpan;
}
else
{
Span tempBuffer = stackalloc byte[(int)headerLength];
headerSlice.CopyTo(tempBuffer);
headerSpan = tempBuffer;
}
// ... (logic to parse typeInfo and bodyLength from headerSpan) ...
uint bodyLength = BinaryPrimitives.ReadUInt32BigEndian(headerSpan.Slice(1 + typeLength, 4));
if (buffer.Length < headerLength + bodyLength) return false;
var bodyBuffer = buffer.Slice(headerLength, bodyLength);
frame = (/*typeInfo*/, bodyBuffer.ToArray(), bodyBuffer.End);
// Consume the parsed frame from the buffer
buffer = buffer.Slice(frame.End);
return true;
}
Клиент подключается и немедленно отправляет два запроса: c:ping и c:server_port.
Журнал сервера: Журнал сервера показывает, что все работает отлично. Он получает оба запроса, отправляет ответы, а затем... задача завершается.
Код: Выделить всё
[14:10:16.772] DBG: New client connected. Session ID: fcc97f89...
[14:10:16.777] DBG: Received complete frame. Type: c:ping, Body Length: 16
[14:10:16.780] DBG: Response sent for request type: c:ping
[14:10:16.782] DBG: Received complete frame. Type: c:server_port, Body Length: 0
[14:10:16.782] DBG: Response sent for request type: c:server_port
[14:10:16.784] DBG: Listening task finished.
Подробнее здесь: [url]https://stackoverflow.com/questions/79806369/c-sharp-tcp-server-with-system-io-pipelines-closes-connection-prematurely-causi[/url]
Мобильная версия