如何使用 Kestrel ConnectionHandler 处理传入的 TCP 消息? [英] How to handle incoming TCP messages with a Kestrel ConnectionHandler?

查看:27
本文介绍了如何使用 Kestrel ConnectionHandler 处理传入的 TCP 消息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为我的 .NET Core 项目创建一个 TCP 侦听器.我正在使用 Kestrel 并为此通过

I want to create a TCP listener for my .NET Core project. I'm using Kestrel and configured a new ConnectionHandler for this via

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

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

When sending messages from a TCP client to the server application the code works fine. The line await pipe.Output.WriteAsync(segment); is acting like an echo for now.

出现一些问题

  • 我应该向客户端发送什么响应以使其不会超时?
  • 我应该什么时候发回响应?当 readResult.IsCompleted 返回 true 时?
  • 我应该如何更改代码以获取客户端发送的整个消息?我是否应该将每个 messageSegment 存储在 List 中,并在 readResult.IsCompleted 返回 true 时将其加入单个字符串?
  • What response should I send back to the client so that it does not run into a timeout?
  • When should I send back the response? When readResult.IsCompleted returns true?
  • How should I change the code to fetch the whole message sent by the client? Should I store each messageSegment in a List<string> and join it to a single string when readResult.IsCompleted returns true?

推荐答案

  1. 完全依赖于协议;在许多情况下,您什么都不做也无妨;在其他情况下,将有特定的乒乓"/乒乓"如果您只想说我还在这里",请发送帧
  2. 何时"完全依赖于协议;等待 readResult.IsCompleted 意味着您正在等待入站套接字标记为关闭,这意味着在客户端关闭其出站套接字之前您不会发送任何内容;对于单次协议,这可能没问题;但在大多数情况下,您需要查找单个入站帧,然后回复该帧(并重复)
  3. 听起来你可能确实在写一个一次性频道,即客户端只向服务器发送一件事,然后:服务器只向客户端发送一件事;在这种情况下,您可以执行以下操作:
  1. that is entirely protocol dependent; in many cases, you're fine to do nothing; in others, there will be specific "ping"/"pong" frames to send if you just want to say "I'm still here"
  2. the "when" is entirely protocol dependent; waiting for 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; for single-shot protocols, that might be fine; but in most cases, you'll want to look for a single inbound frame, and reply to that frame (and repeat)
  3. it sounds like you might indeed be writing a one-shot channel, i.e. the client only sends one thing to the server, and after that: the server only sends one thing to the client; in that case, you do something like:

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 中,并在

Should I store each messageSegment in a List<string> and join it to a single string when

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

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.

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

Because of this, it is common to check IsSingleSegment on the buffer; 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:

  1. 将段线性化为一个连续的缓冲区,可能从 ArrayPool.Shared 租用一个超大的缓冲区,并在 正确的部分使用 UTF8.GetString 租用缓冲区
  2. 在编码上使用 GetDecoder() API,并使用它来填充新字符串,这在旧框架中意味着覆盖新分配的字符串,或者在新框架中意味着使用 string.Create API
  1. linearize the segments into a contiguous buffer, probably leasing an oversized buffer from ArrayPool<byte>.Shared, and use UTF8.GetString on the correct portion of the leased buffer
  2. use the 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 API

坦率地说,1"简单得多.例如(未经测试):

Frankly, "1" is much simpler. 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);
        }
    }
}

这篇关于如何使用 Kestrel ConnectionHandler 处理传入的 TCP 消息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆