XML поверх TCP без рамки сообщения

1

Я должен реализовать 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 (30.03.2015 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 мс, другой метод имеет среднее время 12253 мс. Может кто-нибудь объяснить мне, почему? Я думал, что тот, у кого нет сообщений, будет медленнее...

  • 0
    Если вы не можете внедрить кадрирование сообщений, вам нужно «читать, пока вы не узнаете, что все прочитали». Как это сделать, зависит от вашего текущего кода. Покажите свой код.
  • 0
    Это проблема, код может читать, пока я не найду конечный тег </ DATA, но если он сам закрывается, я не могу его найти ...
Показать ещё 3 комментария
Теги:
tcp

2 ответа

0

У меня была та же проблема - сторонняя система отправляет сообщения в формате XML через TCP, но мое клиентское приложение TCP может получать сообщение частично или несколько сообщений одновременно. Один из моих коллег предложил очень простое и довольно общее решение.

Идея состоит в том, чтобы иметь строковый буфер, который должен быть заполнен символом char из потока TCP, после того, как каждый символ попытается проанализировать содержимое буфера с помощью регулярного синтаксического анализатора XML.Net. Если синтаксический анализатор выдает исключение - продолжайте добавлять символы в буфер. В противном случае - сообщение готово и может быть обработано приложением.

Вот код:

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

Немного комментариев:

  • Необходимо контролировать время жизни строкового буфера, иначе в случае отключения сети старая информация может оставаться в буфере навсегда
  • Здесь серьезная проблема - синтаксический анализ производительности после того, как каждый новый персонаж довольно медленный. Поэтому нужно добавить некоторые оптимизации, такие как синтаксический анализ только после символа '>'.
  • Убедитесь, что этот метод является потокобезопасным, в противном случае несколько потоков могут заливать буфера строк различными 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
        }
    }
0

Передайте NetworkStream инфраструктуре.NET XML. Например, создайте XmlReader из NetworkStream.

К сожалению, я не нашел встроенного способа легко создать XmlDocument из XmlReader котором есть несколько документов. Он жалуется на несколько корневых элементов (что верно). Вам нужно будет обернуть XmlReader и остановить остановку возвращаемых узлов при выполнении первого документа. Вы можете это сделать, отслеживая состояние и просматривая уровень гнездования. Когда уровень вложенности снова равен нулю, первый документ будет выполнен.

Это всего лишь исходный эскиз. Я уверен, что это сработает, и он обрабатывает все возможные XML-документы.

Нет необходимости в этом ужасном коде обработки строк, который у вас есть. Существующий код выглядит довольно медленным, но поскольку этот подход намного лучше, он не имеет никакого смысла комментировать перфомансы. Вам нужно выбросить это.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню