How do I parse XML from NetworkStream via ReadAsyn

2019-08-06 04:53发布

问题:

I am trying to read in XML that I receive as NetworkStream from an OpenFire server via BeginRead of the NetworkStream class in C# with following code (I call it every 1024 bytes to give it more XML parts which are saved in data (which is byte[])).

using (var r = XmlReader.Create(new StringReader(Encoding.UTF8.GetString(data)), 
                                new XmlReaderSettings() { Async = true }))
{                    
    while (await r.ReadAsync())
    {
        switch (r.NodeType)
        {
            case XmlNodeType.Element:
                Console.WriteLine(r.LocalName);
                break;

            case XmlNodeType.Text:
                Console.WriteLine(await r.GetValueAsync());
                break;
        }
    }
}

The 'incomplete' XML I am receiving looks like follows.

<?xml version='1.0' encoding='UTF-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="blah" id="d6c0a0b8" xml:lang="en" version="1.0">
<stream:features>
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism>
<mechanism>ANONYMOUS</mechanism>
<mechanism>CRAM-MD5</mechanism>
</mechanisms>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<auth xmlns="http://jabber.org/features/iq-auth"/>
<register xmlns="http://jabber.org/features/iq-register"/>
</stream:features>

I am getting 2 exceptions.

  • System.Xml.XmlException: '.', hexadecimal value 0x00, is an invalid character
  • System.Xml.XmlException: 'stream' is an undeclared prefix

Why am I getting these exceptions? How do I fix them?

I tried using ConformanceLevel = ConformanceLevel.Fragment but this seems to have nothing to do with it.

Maybe I am using XmlReader and its ReadAsync method completely wrong? Would it be better to read directly from the NetworkStream (if this is the case how is it done correctly)?

Currently I am calling the above code like follows every 1024 bytes (size of the buffer).

private void EndReceive(IAsyncResult asyncResult)
{
    ...
    HandleXMLInput(buffer, nBytes);
    ...
}

Update:

I just realized that no BeginReceive/EndReceive is needed at all. It is sufficient to just open the NetworkStream and then wait for data coming from the network in AsyncRead (woohoo! ;)). The following code sums it up.

socket.BeginConnect(endPoint, new AsyncCallback(EndConnect), null);
private void EndConnect(IAsyncResult asyncResult)
{
    ...
    socket.EndConnect(asyncResult);

    networkStream = new NetworkStream(socket, false);

    HandleXMLInput();

    ...
}

回答1:

It seems that you are decoding the stream elsewhere then feeding it in chunks to XmlReader. Because each time, you're creating a new XmlReader for every chunk, after the first chunk, you'll be feeding broken Xml to a process that expects a well-formed document.

Why not save a whole bunch of coding and heartache by reading the stream directly?

using(var r = XmlReader.Create(myNetworkStream))
{                    
    //await r.ReadAsync() only returns false at the end of the document
    //so this loop will read the whole document
    while (await r.ReadAsync()) 
    {
        switch (r.NodeType)
        {
            case XmlNodeType.Element:
                Console.WriteLine(r.LocalName);
                break;

            case XmlNodeType.Text:
                Console.WriteLine(await r.GetValueAsync());
                break;
        }
    }
}

to be clear: you don't need to do any additional reading of the stream before passing it to the XmlReader. The reader takes care of all the read operations on your behalf when you call ReadAsync.