Search code examples
c#xmlnetwork-programmingxmldocumentnetworkstream

Why does server block when loading XML from a NetworkStream?


I have a simple client and server in C#. The goal is for the client to send an XML document to the server, and then the server should respond with a different XML document. The server is blocking when I try to receive the input/request XML with XmlDocument.Load(NetworkStream).

Server:

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

Client:

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

I tried adding a Flush() in the client after it saves the request XmlDocument to the stream, but that didn't seem to help. Originally I tried having the server read all of the input from the client to a MemoryStream, but then I found that there was no way to signify to the server that all the input was done without disconnecting, which meant that then the client couldn't read its input.

I can send XML input to the server from a file with netcat and everything works fine. This works whether I use XmlDocument.Load(NetworkStream) in the server, or read all the input into a MemoryStream. What is it that netcat is doing in this case that I am not doing in my C# client, and how do I do it in C#? Should I be going about this differently?


Solution

  • Tcp connection is bidirectional, and you can close one half of it while still having the other half open. In this case, you can close client to server half after sending all data, and then you can receive response over server to client half still. You can do it for your example like this:

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

    Don't forget dispose network stream on a server's side after you sent all data:

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

    Actually if whole communication is single request followed by single response - you can use this technique instead of custom protocols over tcp (remember to use timeouts and properly dispose your streams and tcp clients).

    Otherwise, remember that network stream is kind of open connection between client and server - it does not have explicit "end". When you are doing XmlDocument.Load - it will read until it is possible (that is until Read returns with 0 bytes read), so it blocks in your case. You should define your own protocol over tcp, so that you yourself can define the message boundary. Simple way would be - first 4 bytes define the length of the following message. So you read first 4 bytes and then read until that length is reached or timeout occurs.