【问题标题】:C# Read from SslStream continuously (long connection, last for up to days) and Efficiently without infinite loopC# 从 SslStream 连续读取(长连接,持续长达数天)并且有效地没有无限循环
【发布时间】:2020-12-11 13:00:28
【问题描述】:

我对C#完全陌生,需要对客户端和服务器之间发送和接收的数据进行加密,在google了两天后,学会了最好的方法是使用SslStream,我找到的一些答案给出了很好的例子,但它们都是以某种方式假设我们只需要阅读一条消息然后关闭连接,这完全不是我的情况,每当用户触发他的设备通过持久连接发送消息时我都必须阅读。 Microsoft 文档中的一个示例:

static string ReadMessage(SslStream sslStream)
    {
        // Read the  message sent by the client.
        // The client signals the end of the message using the
        // "<EOF>" marker.
        byte [] buffer = new byte[2048];
        StringBuilder messageData = new StringBuilder();
        int bytes = -1;
        do
        {
            // Read the client's test message.
            bytes = sslStream.Read(buffer, 0, buffer.Length);

            // Use Decoder class to convert from bytes to UTF8
            // in case a character spans two buffers.
            Decoder decoder = Encoding.UTF8.GetDecoder();
            char[] chars = new char[decoder.GetCharCount(buffer,0,bytes)];
            decoder.GetChars(buffer, 0, bytes, chars,0);
            messageData.Append (chars);
            // Check for EOF or an empty message. <------   In my case,I don't have EOF
            if (messageData.ToString().IndexOf("<EOF>") != -1)
            {
                break;
            }
        } while (bytes !=0);

        return messageData.ToString();
    }

和其他答案实际上告诉我如何从 SslStream 连续读取,但他们使用无限循环来做到这一点,在服务器端,可能有数千个客户端连接到它,所以可能的性能不佳让我担心,比如这个 : Read SslStream continuously in C# Web MVC 5 project

所以我想知道是否有更好的方法从持久的 SslStream 连接中持续读取。

我知道使用裸套接字我可以使用 SocketAsyncEventArgs 来知道何时准备好新数据,我希望我可以使用 SslStream 来做到这一点,可能我误解了一些东西,任何想法都将不胜感激,谢谢前进。

【问题讨论】:

  • SSLStream 的工作原理docs.microsoft.com/en-us/dotnet/api/…
  • 简单的答案是保持无限循环,但使用await ReadAsync(...。当方法暂停并等待 I/O 时,不会执行任何线程。
  • @JeremyLakeman 这是正确的答案。我在搜索答案时确实看到了ReadAsync,但我误解了当缓冲区中没有数据时它会立即返回零。它实际上只在连接关闭时返回零。非常感谢!

标签: c# sockets ssl sslstream


【解决方案1】:

这是我的尝试。我选择了递归,而不是永远循环。此方法将立即返回,但会在EOF 被命中时触发一个事件并继续阅读:

public static void ReadFromSSLStreamAsync(
    SslStream sslStream,
    Action<string> result,
    Action<Exception> error,
    StringBuilder stringBuilder = null)
{
    const string EOFToken = "<EOF>";

    stringBuilder = stringBuilder ?? new StringBuilder();
    var buffer = new byte[4096];

    try
    {
        sslStream.BeginRead(buffer, 0, buffer.Length, asyncResult =>
        {
            // Read all bytes avaliable from stream and then
            // add them to string builder
            {
                int bytesRead;
                try
                {
                    bytesRead = sslStream.EndRead(asyncResult);
                }
                catch (Exception ex)
                {
                    error?.Invoke(ex);
                    return;
                }

                // Use Decoder class to convert from bytes to
                // UTF8 in case a character spans two buffers.
                var decoder = Encoding.UTF8.GetDecoder();
                var buf = new char[decoder.GetCharCount(buffer, 0, bytesRead)];
                decoder.GetChars(buffer, 0, bytesRead, buf, 0);
                stringBuilder.Append(buf);
            }

            // Find the EOFToken, if found copy all data before the token
            // and send it to event, then remove it from string builder
            {
                int tokenIndex;
                while((tokenIndex = stringBuilder.ToString().IndexOf(EOFToken)) != -1)
                {
                    var buf = new char[tokenIndex];
                    stringBuilder.CopyTo(0, buf, 0, tokenIndex);
                    result?.Invoke(new string(buf));
                    stringBuilder.Remove(0, tokenIndex + EOFToken.Length);
                }
            }

            // Continue reading...
            ReadFromSSLStreamAsync(sslStream, result, error, stringBuilder);
        }, null);
    }
    catch(Exception ex)
    {
        error?.Invoke(ex);
    }
}

你可以这样称呼它:

ReadFromSSLStreamAsync(sslStream, sslData =>
{
    Console.WriteLine($"Finished: {sslData}");
}, error =>
{
    Console.WriteLine($"Errored: {error}");
});

不是TaskAsync,所以你不必在上面await。但它是异步的,因此您的线程可以继续执行其他操作。

【讨论】:

  • 从 SslStream 读取的好模式,但仍然错过了我的一些观点,我需要从流中连续读取,或者说随时都有来自客户端的无休止消息,每一条它们以 结尾。但我认为只需对您的代码稍作改动即可使其正常工作:只需将这一行 ReadUntilEOFAsync(sslStream, result, error, stringBuilder); 移出 _else_curly 大括号,它将继续阅读更多消息。
  • @QiuZhou -- 将其添加进去。现在它将在找到&lt;EOF&gt; 后继续读取而不是停止。
  • @QiuZhou -- 请记住,可能存在这样一种情况,即缓冲区一次读取两个或多个 &lt;EOF&gt; 令牌。我也为该条件添加了代码。
  • @QiuZhou -- 另外,ToString().IndexOf() 可能是一个瓶颈。你可以考虑做一些更优化的事情,像这样:stackoverflow.com/a/12261971/1204153 或者只是废弃StringBuilder 并使用Span&lt;T&gt; 让它变得非常快
猜你喜欢
  • 2019-06-11
  • 2017-02-06
  • 2018-06-28
  • 2012-03-26
  • 1970-01-01
  • 2021-02-12
  • 1970-01-01
  • 2017-03-12
  • 1970-01-01
相关资源
最近更新 更多