C# 如何使用Kestrel ConnectionHandler处理传入的TCP消息?
我想为我的.NET核心项目创建一个TCP侦听器。我正在使用Kestrel并为此via配置了一个新的C# 如何使用Kestrel ConnectionHandler处理传入的TCP消息?,c#,.net-core,tcp,kestrel,C#,.net Core,Tcp,Kestrel,我想为我的.NET核心项目创建一个TCP侦听器。我正在使用Kestrel并为此via配置了一个新的ConnectionHandler kestrelServerOptions.ListenLocalhost(5000, builder => { builder.UseConnectionHandler<MyTCPConnectionHandler>(); }); kestrelServerOptions.ListenLocalhost(5000,生成器=> { Use
ConnectionHandler
kestrelServerOptions.ListenLocalhost(5000, builder =>
{
builder.UseConnectionHandler<MyTCPConnectionHandler>();
});
kestrelServerOptions.ListenLocalhost(5000,生成器=>
{
UseConnectionHandler();
});
到目前为止,我得到的是
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);
}
}
}
内部类MyTCPConnectionHandler:ConnectionHandler
{
公共覆盖异步任务OnConnectedAsync(ConnectionContext连接)
{
IDuplexPipe管道=连接。运输;
PipeReader PipeReader=pipe.Input;
while(true)
{
ReadResult ReadResult=wait pipeReader.ReadAsync();
ReadOnlySequence readResultBuffer=readResult.Buffer;
foreach(readResultBuffer中的ReadOnlyMemory段)
{
//阅读当前消息
string messageSegment=Encoding.UTF8.GetString(segment.Span);
//回音
wait pipe.Output.WriteAsync(段);
}
如果(readResult.IsCompleted)
{
打破
}
pipeReader.AdvanceTo(readResultBuffer.Start、readResultBuffer.End);
}
}
}
当从TCP客户端向服务器应用程序发送消息时,代码工作正常。行wait pipe.Output.WriteAsync(段)代码>现在起着回音的作用
出现了一些问题
- 我应该将什么样的响应发送回客户端,以使其不会超时
- 我应该什么时候回复?当
readResult.IsCompleted
返回true时
- 如何更改代码以获取客户端发送的整个消息?当
readResult.IsCompleted
返回true时,我是否应该将每个messageSegment
存储在列表中,并将其连接到单个字符串
这完全取决于协议;在很多情况下,你可以什么都不做;在其他情况下,如果你只想说“我还在这里”,则会有特定的“ping”/“pong”帧发送
“何时”完全取决于协议;等待readResult.IsCompleted
意味着您正在等待入站套接字标记为关闭,这意味着您在客户端关闭出站套接字之前不会发送任何内容;对于单次激发协议,这可能很好;但在大多数情况下,您需要查找单个入站帧,然后回复该帧(并重复)
听起来您可能确实在编写一个一次性通道,即客户端只向服务器发送一件事,之后:服务器只向客户端发送一件事;在这种情况下,您可以执行以下操作:
while(true)
{
var readResult=await pipeReader.ReadAsync();
如果(readResult.IsCompleted)
{
//TODO:未显示;处理readResult.Buffer
//告诉管道我们把所有东西都吃光了,然后离开
管道读取器.AdvanceTo(readResultBuffer.End,readResultBuffer.End);
打破
}
其他的
{
//等待客户端关闭其出站;告诉
//我们不能消耗任何东西的管道
pipeReader.AdvanceTo(readResultBuffer.Start、readResultBuffer.End);
}
至于:
我是否应该将每个消息段
存储在列表中
并在
这里首先要考虑的是,每个缓冲段不一定包含精确的字符数。因为使用UTF-8,这是一个多字节编码,一个段可能包含在开始和结束部分的字符,所以:解码它比它有更多的参与。
因此,通常在缓冲区上检查IsSingleSegment
;如果这是真的,您可以使用简单的代码:
if(buffer.IsSingleSegment)
{
字符串消息=Encoding.UTF8.GetString(s.FirstSpan);
DoSomethingWith(消息);
}
其他的
{
//…更复杂
}
不连续缓冲区的情况要困难得多;基本上,这里有两种选择:
将段线性化为连续缓冲区,可能从ArrayPool.Shared
租用过大的缓冲区,并在租用缓冲区的正确部分使用UTF8.GetString
在编码上使用GetDecoder()
API,并使用该API填充新字符串,这在旧框架中意味着覆盖新分配的字符串,或者在新框架中意味着使用字符串。创建API
坦率地说,“1”要简单得多。例如(未经测试):
public static string GetString(在这个ReadOnlySequence负载中,
编码=空)
{
编码???=encoding.UTF8;
返回payload.IsSingleSegment?encoding.GetString(payload.FirstSpan)
:GetStringSlow(有效负载、编码);
静态字符串GetStringSlow(在ReadOnlySequence负载中,编码)
{
//线性化
int length=已检查((int)payload.length);
var oversized=ArrayPool.Shared.Rent(长度);
尝试
{
有效载荷。复制到(超大);
返回编码.GetString(超大,0,长度);
}
最后
{
ArrayPool.Shared.Return(超大);
}
}
}