为什么从 NetworkStream 加载 XML 时服务器会阻塞?

Why does server block when loading XML from a NetworkStream?

我有一个简单的 C# 客户端和服务器。目标是让客户端向服务器发送 XML 文档,然后服务器应以不同的 XML 文档响应。当我尝试使用 XmlDocument.Load(NetworkStream).

接收 input/request XML 时,服务器正在阻塞

服务器:

        TcpListener t = new TcpListener(IPAddress.Any, 4444);
        t.Start();
        TcpClient c = t.AcceptTcpClient();
        NetworkStream s = c.GetStream();
        XmlDocument req = new XmlDocument();
        req.Load(s);

        string respString = "<response><data>17</data></response>";
        XmlDocument resp = new XmlDocument();
        resp.LoadXml(respString);
        resp.Save(s);

客户:

        TcpClient t = new TcpClient("localhost", 4444);
        NetworkStream s = t.GetStream();

        string reqStr = "<request><parameters><param>7</param><param>15</param></parameters></request>";
        XmlDocument req = new XmlDocument();
        req.LoadXml(reqStr);

        req.Save(s);

        XmlDocument resp = new XmlDocument();
        resp.Load(s);

        Console.WriteLine(resp.OuterXml);

我尝试在客户端将请求 XmlDocument 保存到流后在客户端中添加一个 Flush(),但这似乎没有帮助。最初我尝试让服务器从客户端读取所有输入到 MemoryStream,但后来我发现没有办法向服务器表明所有输入都已完成而无需断开连接,这意味着客户端无法读取它的输入。

我可以使用 netcat 从文件向服务器发送 XML 输入,一切正常。无论我在服务器中使用 XmlDocument.Load(NetworkStream) 还是将所有输入读入 MemoryStream,这都有效。在这种情况下,netcat 做了什么而我在 C# 客户端中没有做,我如何在 C# 中做?我应该采取不同的方式吗?

Tcp 连接是双向的,你可以关闭其中的一半,而另一半仍然打开。在这种情况下,您可以在发送完所有数据后关闭客户端到服务器的一半,然后您仍然可以通过服务器接收响应到客户端的一半。你可以像这样为你的例子做:

TcpClient t = new TcpClient("localhost", 4444);
NetworkStream s = t.GetStream();
string reqStr = "<request><parameters><param>7</param><param>15</param></parameters></request>";
XmlDocument req = new XmlDocument();
req.LoadXml(reqStr);
req.Save(s);
// important line here! shutdown "send" half of the socket connection.
t.Client.Shutdown(SocketShutdown.Send);
XmlDocument resp = new XmlDocument();
resp.Load(s);
Console.WriteLine(resp.OuterXml);

不要忘记在发送所有数据后在服务器端处理网络流:

TcpListener t = new TcpListener(IPAddress.Loopback, 4444);
t.Start();
TcpClient c = t.AcceptTcpClient();
using (NetworkStream s = c.GetStream()) {
    XmlDocument req = new XmlDocument();
    req.Load(s);                
    Console.WriteLine("Got request: {0}", req.OuterXml);
    string respString = "<response><data>17</data></response>";
    XmlDocument resp = new XmlDocument();
    resp.LoadXml(respString);
    resp.Save(s);
}

实际上,如果整个通信是单个请求后跟单个响应 - 您可以使用此技术而不是通过 tcp 的自定义协议(记住使用超时并正确处理您的流和 tcp 客户端)。

否则,请记住网络流是客户端和服务器之间的一种开放连接 - 它没有显式 "end"。当您执行 XmlDocument.Load 时 - 它会一直读取直到可能(即直到 Read returns 读取 0 个字节),因此它会阻塞您的情况。您应该通过 tcp 定义自己的协议,以便您自己可以定义消息边界。简单的方法是 - 前 4 个字节定义以下消息的长度。因此,您读取前 4 个字节,然后读取直到达到该长度或发生超时。