【问题标题】:C# - Reading HTTP requests with StreamReaderC# - 使用 StreamReader 读取 HTTP 请求
【发布时间】:2017-07-16 12:00:15
【问题描述】:

我正在用 C# 编写一个 TCP 客户端和一个服务器,它们使用手动编写的 HTTP 请求来相互通信。我遇到的麻烦是使用StreamReader 阅读Network Stream。到目前为止,我尝试了很多方法,但无济于事。

我从 TCP 客户端收到的请求有多种形式。对于更新数据库,请求如下所示(CRLF 是我用来表示"\r\n" 字符串的常量):

HTTP 1.0:

“POST /”+名称+“HTTP/1.0”+CRLF+“内容长度:”+长度+ CRLF + CRLF + 位置;

HTTP 1.1:

“POST/HTTP/1.1”+CRLF+主机名+“内容长度:”+长度+ CRLF + CRLF + nameLocString;

请求的格式正确,并且客户端正确发送它们 - 我已经在我有权访问的服务器上对此进行了测试,该服务器可以毫无问题地响应它们。

我遇到的问题与我的 TCP 侦听器代码有关。为了避免发布整个代码,我将仅包含有问题的代码部分(通过调试发现)。

服务器代码:

NetworkStream socketStream = new NetworkStream(connection);
StreamReader sr = new StreamReader(socketStream);

string input = ReadAllLinesWithNull(sr); // reading version 1
string input = ReadAllLinesWithEndOfStream(sr);  // reading version 2
string input = ReadAllLinesWithPeek(sr);  // reading version 3
string input = sr.ReadToEnd();  // reading version 4

而使用的方法有:

static string ReadAllLinesWithNull(StreamReader sr)
{
    string input;
    string nextLine;
    input = sr.ReadLine();
    while ((nextLine = sr.ReadLine()) != null)
    {
        Console.WriteLine(input);
        input += nextLine;
    }
    sr.Close();
    return input;
}

static string ReadAllLinesWithEndOfStream(StreamReader sr)
{
    string input = "";
    while (!sr.EndOfStream)
    {
        input += sr.ReadLine();
    }
    sr.Close();
    return input;
}

static string ReadAllLinesWithPeek(StreamReader sr)
{
    string input = "";
    while (sr.Peek() >= 0)
    {
        input += sr.ReadLine();
    }
    sr.Close();
    return input;
}

这些阅读方法都不起作用。设置了连接超时后,我收到了 IO 异常,即读取时间过长/连接被强制关闭。我关闭了超时,读取花费了无限的时间。

感谢使用ReadLine()s,我能够为所有版本的协议挑选出最终挂起的位置,并且发现当有两个 CRLF 的集群 ("\r\n\r\n") 时,Stream Reader无法应对并陷入困境。

您对如何解决这个问题有什么建议吗?我需要按照规范使用具有多个 CRLF 的版本。

如果您需要任何其他信息,我会尽量以 sson 的形式提供。

【问题讨论】:

  • 您能否发布“无法应对并卡住”的代码,因为您发布的内容对我来说很好。请注意ReadLinedoes not include any terminating returns or linefeeds,因此您发布的内容将返回所有连接的内容,并删除了回车和换行符。
  • @DourHighArch 我提供的代码对我来说都是失败的。我检查并且客户端正确地将提到的 HTTP 请求写入流并刷新它们,然后当我使用任何 ReadLine 方法读取时,我从来没有得到任何超过 Content-Length: + length + CRLF + CRLF 的内容。当我设置超时时,它要么无限期地读取,要么抛出异常。感谢您提供有关 ReadLine 的说明。我首先想让它读起来尽可能简单,然后再看看正确的读法。我用新的简单服务器测试了读取(只是读取和回显),它有同样的问题。

标签: c# network-programming streamreader tcplistener networkstream


【解决方案1】:

最后我找到了解决问题的方法。而不是使用

static string ReadAllLinesWithPeek(StreamReader sr)
{
    string input = "";
    while (sr.Peek() >= 0)
    {
        input += sr.ReadLine();
    }
    sr.Close();
    return input;
}

我不得不使用

static string ReadAllLinesWithPeek(StreamReader sr)
{
    string input = "";
    while (sr.Peek() >= 0)
    {
        input += (char) sr.Read();
    }
    return input;
}

我仍然不确定为什么按行读取输入不起作用,但一次按字符读取时,它确实起作用。

【讨论】:

    【解决方案2】:

    如果当前没有可用数据并且另一方尚未关闭该通道,则NetworkStream 阻止Read 操作。 TCP 本身没有 消息 的概念 - 该问题将在 HTTP 级别解决。

    对于 HTTP,您可以继续阅读,直到您的数据包含 \r\n\r\n 序列,它将标头与正文分开。如何处理正文取决于存在哪些标头:

    • Transfer-Encoding: chunked 表示发送方将发送数据块,并以长度为 0 的块结束
    • Content-Length 在不使用块时应该存在,然后您可以准确读取那么多字节的数据
    • GET 请求不应有正文,如果未设置上述标头,您可能会假设为这一点
    • Connection: close可用于响应,表示发送完所有响应数据后关闭连接

    如你所见,StreamReader.ReadLine() 可以很好地解析头部,它也非常适合读取块,但它不能用于读取固定长度的正文。

    我不知道从以前由 StreamReader 读取的流中读取数据有多可靠(它可能可以将一些数据提前读取到它的缓冲区),但是在它们周围拍using 块只会导致除非您pick that one constructor overload,否则基础流将被关闭。

    【讨论】:

    • 我以前错了,所以我不接受你的回答。通过遵循它,我的问题只是进一步向下移动。感谢您的努力并写下我的问题的答案。
    • 我不介意你不接受我的回答,但是之前你评论说这个回答启发你打电话给StreamReader.Close() 而不是使用using 块——这真的不是我想给你的方向中。如果您对我的回答有疑问,可以发表评论,我会更新答案。
    猜你喜欢
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 2020-08-30
    • 2016-08-06
    • 1970-01-01
    相关资源
    最近更新 更多