C# - 使用 StreamReader 读取 HTTP 请求
C# - Reading HTTP requests with StreamReader
我正在用 C# 编写 TCP 客户端和服务器,它们使用手动编写的 HTTP 请求相互通信。我遇到的麻烦是使用 StreamReader
从 Network 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。
我正在用 C# 编写 TCP 客户端和服务器,它们使用手动编写的 HTTP 请求相互通信。我遇到的麻烦是使用 StreamReader
从 Network 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。