[英]How to handle incoming TCP messages with a Kestrel ConnectionHandler?
I want to create a TCP listener for my .NET Core project.我想为我的 .NET Core 项目创建一个 TCP 侦听器。 I'm using Kestrel and configured a new
ConnectionHandler
for this via我正在使用 Kestrel 并为此配置了一个新的
ConnectionHandler
kestrelServerOptions.ListenLocalhost(5000, builder =>
{
builder.UseConnectionHandler<MyTCPConnectionHandler>();
});
So what I have so far is所以到目前为止我所拥有的是
internal class MyTCPConnectionHandler : ConnectionHandler
{
public override async Task OnConnectedAsync(ConnectionContext connection)
{
IDuplexPipe pipe = connection.Transport;
PipeReader pipeReader = pipe.Input;
while (true)
{
ReadResult readResult = await pipeReader.ReadAsync();
ReadOnlySequence<byte> readResultBuffer = readResult.Buffer;
foreach (ReadOnlyMemory<byte> segment in readResultBuffer)
{
// read the current message
string messageSegment = Encoding.UTF8.GetString(segment.Span);
// send back an echo
await pipe.Output.WriteAsync(segment);
}
if (readResult.IsCompleted)
{
break;
}
pipeReader.AdvanceTo(readResultBuffer.Start, readResultBuffer.End);
}
}
}
When sending messages from a TCP client to the server application the code works fine.当从 TCP 客户端向服务器应用程序发送消息时,代码工作正常。 The line
await pipe.Output.WriteAsync(segment);
该行
await pipe.Output.WriteAsync(segment);
is acting like an echo for now.现在就像一个回声。
Some questions come up出现了一些问题
readResult.IsCompleted
returns true?readResult.IsCompleted
返回 true 时?messageSegment
in a List<string>
and join it to a single string when readResult.IsCompleted
returns true?messageSegment
存储在List<string>
并在readResult.IsCompleted
返回 true 时将其连接到单个字符串吗?readResult.IsCompleted
means that you're waiting for the inbound socket to be marked as closed, which means you won't send anything until the client closes their outbound socket;readResult.IsCompleted
意味着您正在等待入站套接字被标记为关闭,这意味着在客户端关闭其出站套接字之前您不会发送任何内容; for single-shot protocols, that might be fine;while (true)
{
var readResult = await pipeReader.ReadAsync();
if (readResult.IsCompleted)
{
// TODO: not shown; process readResult.Buffer
// tell the pipe that we consumed everything, and exit
pipeReader.AdvanceTo(readResultBuffer.End, readResultBuffer.End);
break;
}
else
{
// wait for the client to close their outbound; tell
// the pipe that we couldn't consume anything
pipeReader.AdvanceTo(readResultBuffer.Start, readResultBuffer.End);
}
As for:至于:
Should I store each
messageSegment
in aList<string>
and join it to a single string when我是否应该将每个
messageSegment
存储在List<string>
并在以下情况下将其加入单个字符串
The first thing to consider here is that it is not necessarily the case that each buffer segment contains an exact number of characters.这里首先要考虑的是,不一定每个缓冲区段都包含确切数量的字符。 Since you are using UTF-8, which is a multi-byte encoding, a segment might contain fractions of characters at the start and end, so: decoding it is a bit more involved than that.
由于您使用的是 UTF-8,这是一种多字节编码,一个段可能在开始和结束时包含部分字符,因此:解码它比这更复杂。
Because of this, it is common to check IsSingleSegment
on the buffer;因此,在缓冲区上检查
IsSingleSegment
是很常见的; if this is true, you can just use simple code:如果这是真的,你可以使用简单的代码:
if (buffer.IsSingleSegment)
{
string message = Encoding.UTF8.GetString(s.FirstSpan);
DoSomethingWith(message);
}
else
{
// ... more complex
}
The discontiguous buffer case is much harder;不连续的缓冲区情况要困难得多; basically, you have two choices here:
基本上,您在这里有两个选择:
ArrayPool<byte>.Shared
, and use UTF8.GetString
on the correct portion of the leased bufferArrayPool<byte>.Shared
租用一个超大的缓冲区,并在租用的缓冲区的正确部分使用UTF8.GetString
GetDecoder()
API on the encoding, and use that to populate a new string, which on older frameworks means overwriting a newly allocated string, or in newer frameworks means using the string.Create
APIGetDecoder()
API,并使用它来填充新字符串,这在旧框架中意味着覆盖新分配的字符串,或者在新框架中意味着使用string.Create
API Frankly, "1" is much simpler.坦率地说,“1”要简单得多。 For example (untested):
例如(未经测试):
public static string GetString(in this ReadOnlySequence<byte> payload,
Encoding encoding = null)
{
encoding ??= Encoding.UTF8;
return payload.IsSingleSegment ? encoding.GetString(payload.FirstSpan)
: GetStringSlow(payload, encoding);
static string GetStringSlow(in ReadOnlySequence<byte> payload, Encoding encoding)
{
// linearize
int length = checked((int)payload.Length);
var oversized = ArrayPool<byte>.Shared.Rent(length);
try
{
payload.CopyTo(oversized);
return encoding.GetString(oversized, 0, length);
}
finally
{
ArrayPool<byte>.Shared.Return(oversized);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.