C# - 使用 StreamReader 读取 HTTP 请求

C# - Reading HTTP requests with StreamReader

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

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

HTTP 1.0:

"POST /" + name + " HTTP/1.0" + CRLF + "Content-Length: " + length + CRLF + CRLF + location;

HTTP 1.1:

"POST / HTTP/1.1" + CRLF + hostname + "Content-Length: " + length + 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;
}

None 这些阅读方法奏效了。设置连接超时后,我收到了 IO 异常,表明 read/the 连接被强行关闭花费的时间太长。我关闭了超时,Read 花费了无限长的时间。

感谢使用 ReadLine()s 我能够为所有版本的协议挑出它最终挂起的地方并且 发现当有两个 CRLF 的集群时("\r\n\r\n"),Stream Reader 无法处理并卡住。

关于如何解决这个问题,您有什么建议吗?我需要使用规范中包含多个 CRLF 的版本。

如果您需要任何其他信息,我会尽量提供。

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

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

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

如果当前 没有可用数据并且另一方尚未关闭该通道,

A NetworkStream 会阻止Read 操作。 TCP 本身没有 消息的概念 - 该问题将在 HTTP 级别解决。

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

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

正如你所看到的,StreamReader.ReadLine() 在解析 header 时效果很好,它也非常适合读取块,但它不能用于读取 fixed-length body。

我不知道从之前由 StreamReader 读取的流中读取有多可靠(它可能会提前读取一些数据到它的缓冲区),但是拍打 using它们周围的块只会导致底层流关闭,除非你 pick that one constructor overload