【问题标题】:How to handle incoming TCP messages with a Kestrel ConnectionHandler?如何使用 Kestrel ConnectionHandler 处理传入的 TCP 消息?
【发布时间】:2020-12-28 14:52:40
【问题描述】:

我想为我的 .NET Core 项目创建一个 TCP 侦听器。我正在使用 Kestrel 并为此配置了一个新的 ConnectionHandler

kestrelServerOptions.ListenLocalhost(5000, builder =>
{
    builder.UseConnectionHandler<MyTCPConnectionHandler>();
});

所以我目前拥有的是

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);
        }
    }
}

从 TCP 客户端向服务器应用程序发送消息时,代码可以正常工作。 await pipe.Output.WriteAsync(segment); 行现在就像一个回声。

出现一些问题

  • 我应该向客户端发回什么响应,以免超时?
  • 我应该什么时候发回响应? readResult.IsCompleted 什么时候返回 true?
  • 我应该如何更改代码以获取客户端发送的整个消息?我是否应该将每个messageSegment 存储在List&lt;string&gt; 中,并在readResult.IsCompleted 返回true 时将其加入单个字符串?

【问题讨论】:

    标签: c# .net-core tcp kestrel


    【解决方案1】:
    1. 完全依赖于协议;在很多情况下,你什么都不做也没关系;在其他情况下,如果您只想说“我还在这里”,则会发送特定的“ping”/“pong”帧
    2. “何时”完全取决于协议; waiting for readResult.IsCompleted 表示您正在等待将入站套接字标记为已关闭,这意味着您在客户端关闭其出站套接字之前不会发送任何内容;对于单次协议,这可能很好;但在大多数情况下,您需要查找单个入站帧,然后回复该帧(并重复)
    3. 听起来您可能确实在编写一次性通道,即客户端只向服务器发送一件事,然后:服务器只向客户端发送一件事; 在这种情况下,你可以这样做:
    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);
        }
    

    至于:

    我是否应该将每个 messageSegment 存储在 List&lt;string&gt; 中并在何时将其加入单个字符串

    这里首先要考虑的是,每个缓冲区段不一定包含确切数量的字符。由于您使用的是 UTF-8,这是一种多字节编码,因此一个段可能在开头和结尾包含部分字符,因此:解码比这更复杂。

    因此,在缓冲区上检查IsSingleSegment 是很常见的;如果这是真的,您可以使用简单的代码:

    if (buffer.IsSingleSegment)
    {
        string message = Encoding.UTF8.GetString(s.FirstSpan);
        DoSomethingWith(message);
    }
    else
    {
        // ... more complex
    }
    

    不连续的缓冲区情况要困难得多;基本上,你有两个选择:

    1. 将这些段线性化为一个连续的缓冲区,可能从ArrayPool&lt;byte&gt;.Shared 租用一个超大的缓冲区,并在租用缓冲区的正确部分上使用UTF8.GetString
    2. 在编码上使用GetDecoder() API,并使用它来填充新字符串,这在旧框架上意味着覆盖新分配的字符串,或者在新框架中意味着使用string.Create API

    坦率地说,“1”要简单得多。例如(未经测试):

    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);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多