Xml over tcp没有消息帧

时间:2015-03-27 12:41:17

标签: c# xml tcp

我必须实现一个tcp连接,其中传递原始xml数据。 不幸的是没有消息框架,我现在这真的很糟糕,但我必须处理这个...

消息看起来像这样:

<?xml version="1.0" encoding="utf-8"?>
<DATA></DATA>

或者

<?xml version="1.0" encoding="utf-8"?>
<DATA />

现在我必须收到可能有自闭标记的消息。消息始终相同,它总是像xml描述和内部xml的数据标记,即消息内容。 因此,如果没有自闭标签,这将很容易,但我怎么能同时阅读?

顺便说一句,我正在使用TcpListener。

编辑: 如果没有自闭标签,一切都很好。

            if (_clientSocket != null)
            {
                NetworkStream networkStream = _clientSocket.GetStream();
                _clientSocket.ReceiveTimeout = 100; // 1000 miliseconds

                while (_continueProcess)
                {
                    if (networkStream.DataAvailable)
                    {
                        bool isMessageComplete = false;
                        String messageString = String.Empty;
                        while (!isMessageComplete)
                        {
                            var bytes = new byte[_clientSocket.ReceiveBufferSize];
                            try
                            {
                                int bytesReaded = networkStream.Read(bytes, 0, (int) _clientSocket.ReceiveBufferSize);
                                if (bytesReaded > 0)
                                {
                                    var data = Encoding.UTF8.GetString(bytes, 0, bytesReaded);
                                    messageString += data;

                                    if (messageString.IndexOf("<DATA", StringComparison.OrdinalIgnoreCase) > 0 &&
                                        messageString.IndexOf("</DATA", StringComparison.OrdinalIgnoreCase) > 0)
                                    {
                                        isMessageComplete = true;
                                    }

                                }
                            }
                            catch (IOException)
                            {
                                // Timeout  
                            } 
                            catch (SocketException)
                            {
                                Console.WriteLine("Conection is broken!");
                                break;
                            }
                        }

                    }

                    Thread.Sleep(200);
                } // while ( _continueProcess )
                networkStream.Close();
                _clientSocket.Close();
            }

编辑2(2015年3月30日12:00)

不幸的是,不可能使用某种消息帧。 所以我最终使用了这部分代码(DATA是我的根节点):

            if (_clientSocket != null)
            {
                NetworkStream networkStream = _clientSocket.GetStream();
                _clientSocket.ReceiveTimeout = 100; 
                string data = string.Empty;

                while (_continueProcess)
                {
                    try
                    {
                        if (networkStream.DataAvailable)
                        {

                            Stopwatch sw = new Stopwatch();
                            sw.Start();
                            var bytes = new byte[_clientSocket.ReceiveBufferSize];

                            int completeXmlLength = 0;
                            int bytesReaded = networkStream.Read(bytes, 0, (int) _clientSocket.ReceiveBufferSize);
                            if (bytesReaded > 0)
                            {
                                message.AddRange(bytes);
                                data += Encoding.UTF8.GetString(bytes, 0, bytesReaded);

                                if (data.IndexOf("<?", StringComparison.Ordinal) == 0)
                                {
                                    if (data.IndexOf("<DATA", StringComparison.Ordinal) > 0)
                                    {
                                        Int32 rootStartPos = data.IndexOf("<DATA", StringComparison.Ordinal);
                                        completeXmlLength += rootStartPos;
                                        var root = data.Substring(rootStartPos);
                                        int rootCloseTagPos = root.IndexOf(">", StringComparison.Ordinal);
                                        Int32 rootSelfClosedTagPos = root.IndexOf("/>", StringComparison.Ordinal);
                                        // If there is an empty tag that is self closed.
                                        if (rootSelfClosedTagPos > 0)
                                        {
                                            string rootTag = root.Substring(0, rootSelfClosedTagPos +1);
                                            // If there is no '>' between the self closed tag and the start of '<DATA'
                                            // the root element is empty.
                                            if (rootTag.IndexOf(">", StringComparison.Ordinal) <= 0)
                                            {
                                                completeXmlLength += rootSelfClosedTagPos;
                                                string messageXmlString = data.Substring(0, completeXmlLength + 1);
                                                data = data.Substring(messageXmlString.Length);
                                                try
                                                {
                                                    // parse complete xml.
                                                    XDocument xmlDocument = XDocument.Parse(messageXmlString);
                                                }
                                                catch(Exception)
                                                {
                                                    // Invalid Xml.
                                                }
                                                continue;                                               
                                            }
                                        }
                                        if (rootCloseTagPos > 0)
                                        {
                                            Int32 rootEndTagStartPos = root.IndexOf("</DATA", StringComparison.Ordinal);
                                            if (rootEndTagStartPos > 0)
                                            {
                                                var endTagString = root.Substring(rootEndTagStartPos);
                                                completeXmlLength += rootEndTagStartPos;
                                                Int32 completeEndPos = endTagString.IndexOf(">", StringComparison.Ordinal);
                                                if (completeEndPos > 0)
                                                {
                                                    completeXmlLength += completeEndPos;
                                                    string messageXmlString = data.Substring(0, completeXmlLength + 1);
                                                    data = data.Substring(messageXmlString.Length);
                                                    try
                                                    {
                                                        // parse complete xml.
                                                        XDocument xmlDocument = XDocument.Parse(messageXmlString);
                                                    }
                                                    catch(Exception)
                                                    {
                                                        // Invalid Xml.
                                                    }                                                           
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            sw.Stop();
                            string timeElapsed = sw.Elapsed.ToString();

                        }
                    }
                    catch (IOException)
                    {
                        data = String.Empty;  
                    }
                    catch (SocketException)
                    {
                        Console.WriteLine("Conection is broken!");
                        break;
                    }
                }

这个代码我曾经使用过,如果是某种消息框架,在这种情况下是4个字节的消息长度:

            if (_clientSocket != null)
            {
                NetworkStream networkStream = _clientSocket.GetStream();
                _clientSocket.ReceiveTimeout = 100; 
                string data = string.Empty;

                while (_continueProcess)
                {
                    try
                    {
                        if (networkStream.DataAvailable)
                        {
                            Stopwatch sw = new Stopwatch();
                            sw.Start();

                            var lengthBytes = new byte[sizeof (Int32)];
                            int bytesReaded = networkStream.Read(lengthBytes, 0, sizeof (Int32) - offset);
                            if (bytesReaded > 0)
                            {
                                offset += bytesReaded;
                                message.AddRange(lengthBytes.Take(bytesReaded));
                            }
                            if (offset < sizeof (Int32))
                            {
                                continue;
                            }
                            Int32 length = BitConverter.ToInt32(message.Take(sizeof(Int32)).ToArray(), 0);

                            message.Clear();
                            while (length > 0)
                            {
                                Int32 bytesToRead = length < _clientSocket.ReceiveBufferSize ? length : _clientSocket.ReceiveBufferSize;
                                byte[] messageBytes = new byte[bytesToRead];
                                bytesReaded = networkStream.Read(messageBytes, 0, bytesToRead);
                                length = length - bytesReaded;
                                message.AddRange(messageBytes);
                            }

                            try
                            {
                                string xml = Encoding.UTF8.GetString(message.ToArray());
                                XDocument xDocument = XDocument.Parse(xml);
                            }
                            catch (Exception ex)
                            {
                                // Invalid Xml.
                            }
                            sw.Stop();
                            string timeElapsed = sw.Elapsed.ToString();                                
                        }
                    }
                    catch (IOException)
                    {
                        data = String.Empty;  
                    }
                    catch (SocketException)
                    {
                        Console.WriteLine("Conection is broken!");
                        break;
                    }
                }

就像你可以看到我想测量经过的时间,看看女性方法有更好的表现。奇怪的是,没有消息帧的方法的平均时间为0.2290毫秒,另一个方法的平均时间为1,2253毫秒。 有人能解释一下为什么吗?我认为没有消息框架的那个会更慢......

2 个答案:

答案 0 :(得分:0)

NetworkStream交给.NET XML基础架构。例如,从XmlReader创建NetworkStream

很遗憾,我没有找到一种内置方法可以轻松地从XmlDocument创建一个XmlReader,其中包含多个文档。它抱怨多个根元素(这是正确的)。您需要包装XmlReader并使其在第一个文档完成时停止返回节点。您可以通过跟踪某些状态并查看嵌套级别来实现。当嵌套级别再次为零时,第一个文档就完成了。

这只是原始草图。我很确定这会起作用,它会处理所有可能的XML文档。

你不需要这个可怕的字符串处理代码。现有的代码看起来也很慢,但由于这种方法要好得多,因此没有任何意义来评论性能问题。你需要扔掉它。

答案 1 :(得分:0)

我遇到了同样的问题 - 第三方系统通过TCP以XML格式发送消息,但我的TCP客户端应用程序可能会同时收到部分或多条消息的消息。我的一位同事提出了非常简单和非常通用的解决方案。

我们的想法是在每个char尝试使用常规的.Net XML解析器解析缓冲区内容之后,有一个字符串缓冲区,该缓冲区应该由来自TCP流的char填充char。如果解析器抛出异常 - 继续将chars添加到缓冲区。否则 - 消息已准备就绪,可由应用程序处理。

以下是代码:

    private object _dataReceiverLock = new object();
    private string _messageBuffer;
    private Stopwatch _timeSinceLastMessage = new Stopwatch();
    private List<string> NormalizeMessage(string rawMsg)
    {
        lock (_dataReceiverLock)
        {
            List<string> result = new List<string>();


            //following code prevents buffer to store too old information
            if (_timeSinceLastMessage.ElapsedMilliseconds > _settings.ResponseTimeout)
            {
                _messageBuffer = string.Empty;
            }
            _timeSinceLastMessage.Restart();



            foreach (var ch in rawMsg)
            {
                _messageBuffer += ch;
                if (ch == '>')//to avoid extra checks
                {
                    if (IsValidXml(_messageBuffer))
                    {
                        result.Add(_messageBuffer);
                        _messageBuffer = string.Empty;
                    }
                }
            }

            return result;
        }
    }

    private bool IsValidXml(string xml)
    {
        try
        {
            //fastest way to validate XML format correctness
            using (XmlTextReader reader = new XmlTextReader(new StringReader(xml)))
            {
                while (reader.Read()) { }
            }

            return true;
        }
        catch
        {
            return false;
        }
    }

很少有评论:

  • 需要控制字符串缓冲区的生命周期,否则如果网络断开,旧信息可能会永久保留在缓冲区中
  • 这里的主要问题是性能 - 在每个新角色很慢之后进行解析。因此需要添加一些优化,例如仅在&#39;&gt;&#39;之后解析。字符。
  • 确保此方法是线程安全的,否则多个线程可能会使用不同的XML片段来填充字符串缓冲区。

用法很简单:

    private void _tcpClient_DataReceived(byte[] data)
    {
        var rawMsg = Encoding.Unicode.GetString(data);
        var normalizedMessages = NormalizeMessage(rawMsg);

        foreach (var normalizedMessage in normalizedMessages)
        {
            //TODO: your logic
        }
    }