C# - 使用StreamReader读取HTTP请求

4
我正在使用C#编写TCP客户端和服务器,它们使用手动编写的HTTP请求进行通信。我遇到的问题是使用StreamReader从网络流中读取数据。到目前为止,我尝试了许多方法,但都没有成功。
我从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;
}

没有任何一种读取方法可行。设置连接超时后,我遇到了IO异常,提示读取时间过长或连接被强制关闭。我关闭了超时设置,但读取需要不确定的时间。
使用ReadLine(),我能够找出所有协议版本最终停顿的位置,并发现当两个CRLFs("\r\n\r\n")在一起时,流读取器无法处理并会卡住。
你有什么建议来解决这个问题吗?因为规范中要求使用多个CRLFs的版本。
如果需要额外的信息,我会尽快提供。

你能否发布一些“无法处理并且会卡住”的代码,因为你发布的那些对我来说运行得很好。请注意,ReadLine不包含任何终止返回或换行符,因此你发布的内容将返回所有已删除返回和换行符的连接内容。 - Dour High Arch
@DourHighArch 我提供的代码是我遇到问题的全部代码。我检查了客户端,它正确地将HTTP请求写入流并刷新它们,但当我使用任何ReadLine方法读取时,我永远无法获得超过Content-Length: + length + CRLF + CRLF的任何内容。它要么无限期地读取,要么在设置超时时抛出异常。谢谢你提醒我关于ReadLine的事情。我首先想让它尽可能简单地读取,然后再考虑正确的读取方式。我使用新的简单服务器(只读取和回显)测试了读取功能,但仍然存在同样的问题。 - Alexander Rossa
2个回答

4
最终,我找到了解决我的问题的方法。不再使用
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;
}

我仍然不确定为什么按行读取输入不起作用,但一次一个字符地读取时就有效。


1

NetworkStream 在当前没有可用数据且对方尚未关闭通道时,Read 操作会阻塞。TCP 本身没有消息的概念 - 这个问题需要在 HTTP 层解决。

对于 HTTP,您可以持续读取数据,直到数据中包含一个 \r\n\r\n 序列,该序列将头部与正文分开。如何处理正文取决于哪些头部存在:

  • Transfer-Encoding: chunked 表示发送方将发送数据块,并以长度为 0 的块结束
  • Content-Length 应在不使用块时出现,然后可以准确地读取那么多字节的数据
  • GET 请求 不应该有正文,如果上述头部未设置,则可以假定此情况
  • Connection: close 可用于响应,表示在发送所有响应数据后将关闭连接
正如您所看到的,StreamReader.ReadLine() 在解析头部方面效果很好,并且非常适合读取块,但不能用于读取固定长度的正文。
我不知道从以前由 StreamReader 读取的流中读取有多可靠(它可能会向前读取一些数据到其缓冲区),但是在它们周围添加 using 块只会导致底层流被关闭,除非您 选择那个构造函数重载

我之前的想法是错误的,这就是为什么我取消了你的答案。按照你的方法,我的问题只是被推迟了。感谢你的努力和回答我的问题。 - Alexander Rossa
我不介意你取消接受我的答案,但之前你评论说这个答案启发了你调用StreamReader.Close()而不是使用using块——这真的不是我想引导你的方向。如果你对我的答案有疑问,可以在评论中提出,我会更新答案。 - C.Evenhuis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接