Отправка xml-сообщения по частям через TCP-сокет с использованием Qt

Мы пишем проект, в котором есть клиент, который генерирует XML-запросы, отправляет их на сервер, который анализирует запрос и возвращает запрошенную информацию в XML-строке.

Приложение отлично работает, когда xml-ответы небольшие, но когда они превышают 2500 символов, они иногда обрезаются на стороне клиента. Я иногда говорю, потому что, когда клиент и сервер работают на одной машине и общаются через домашний адрес 127.0.0.1, ответы анализируются очень хорошо. Однако, когда клиент и сервер находятся на разных компьютерах и обмениваются данными по локальной сети, это происходит, когда клиент обрезает сообщение примерно до 2500 символов.

Связь осуществляется через сокеты TCP. Мы используем Qt, клиент имеет qTCPsocket, а сервер - qTCPserver и указатель на qtcpsocket.

Мы думаем, что возможным решением нашей проблемы является отправка xml по частям, либо по количеству символов, либо по тегу. В то время как нам легко разбить сообщение на части, отправка частей и чтение или компиляция клиентом или сервером частей в один XML-запрос вызывает у нас проблемы.

В качестве примера мы хотели проверить, чтобы клиент отправлял запрос в несколько частей.

Вот наш клиентский вызов функции для отправки запроса. xmlReq генерируется в другом месте и передается внутрь. В качестве примера разбивки сообщения на части мы убираем закрывающий тег из запроса xml, а затем отправляем его как другой фрагмент.

QString ClientConnection::sendRequest(QString xmlReq)
{

    this->xmlRequest = xmlReq;

    QHostAddress addr(address);

    QList<QString> messagePieces;
    xmlRequest.remove("</message>");

    messagePieces.append(xmlRequest);
    messagePieces.append("</message>");


    client.connectToHost(addr,6789);

    if(client.waitForConnected(30000))
    {
        for(int i = 0; i < messagePieces.length();i++)
        {  
            client.write(messagePieces[i].toAscii(),messagePieces[i].length()+1);
            qDebug() << "Wrote: " << messagePieces[i];
        }
    }


    char message[30000] = {0};

    xmlReply = "";

    if(client.waitForReadyRead(30000)){


        client.read(message,client.bytesAvailable());


    }else{
        xmlReply = "Server Timeout";
    }

    client.close();
    xmlReply = (QString) message;

    return xmlReply;

}

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

Это код, который запускает сервер.

//Start the server, pass it the handler so it can perform queries
    connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
    server.listen(QHostAddress::Any, 6789);

Когда он получает новое соединение, он вызывает слот acceptConnection, который выглядит следующим образом

    void CETServer::acceptConnection()
{
    client = server.nextPendingConnection();
    connect(client, SIGNAL(readyRead()), this, SLOT(startRead()));
}

StartRead выглядит так:

void CETServer::startRead()
{
    char buffer[1024*30] = {0};

    client->read(buffer, client->bytesAvailable());

    QString readIn;

    readIn = (QString) buffer;

    ui->statusText->appendPlainText("Received: " + readIn);

    //New messages in will be opened with the xml version tag
    //if we receive said tag we need to clear our query
    if (readIn.contains("<?xml version =\"1.0\"?>",Qt::CaseSensitive))
    {
        xmlQuery = "";
    }

    //add the line received to the query string
    xmlQuery += readIn;

    //if we have the clsoe message tag in our query it is tiem to do stuf with the query
    if(xmlQuery.contains("</message>"))
    {
        //do stuff with query

        ui->statusText->appendPlainText("Query received:" + xmlQuery);

        QString reply = this->sqLite->queryDatabase(xmlQuery);
        xmlQuery = "";

        this->commandStatus(reply);

        if(client->isWritable()){
            //write to client
            client->write(reply.toAscii(),reply.length()+1);
            ui->statusText->appendPlainText("Sent to client: " + reply);
            client->close();

        }
    }}

На мой взгляд, начальное чтение кодируется таким образом, что каждый раз, когда клиент пишет сообщение, сервер читает его и присоединяет его к xmlRequest, который хранит сервер. Если сообщение содержит закрывающий тег xml, то он обрабатывает запрос.

Что происходит, однако, если клиент выполняет последовательные записи, сервер не читает их все, только первое, и никогда не получает закрывающий тег xml и, следовательно, не обрабатывает запросы.

Мне нужно ответить на вопрос: почему сервер не отвечает на множественные записи клиентов? Как мне сделать так, чтобы я мог отправить строку XML, разбитую на части, и чтобы сервер прочитал все части и снова превратил их в одну строку?

3 ответа

Решение

Это происходит из-за "потоковой" природы протокола TCP. Данные разбиты на множество пакетов, и в вашем приложении вы действительно читаете только часть из них (bytesAvailable() необязательно равно количеству байтов, отправленных другим хостом, просто количество байтов доступно в буфер сокетов). Что вам нужно сделать, это установить простой протокол между клиентом и сервером. Например, клиент сначала отправляет символ STX, затем XML, а затем символ ETX. Когда сервер видит символ STX, он считывает все в буфер вплоть до символа EXT. Другой подход - отправить 4-байтовое целое число в сетевом порядке байтов, указывающее размер данных XML в байтах, затем отправить XML. Другой хост должен получить целое число, преобразовать его в свой собственный метеородер, а затем прочитать указанный объем данных из сокета в буфер.

В TCP существует так называемый максимальный размер сегмента. Перед инициализацией передачи данных обе стороны выбирают MSS на этапе установления связи SYN. Это причина, почему ваши данные разделяются.

У вас есть только один client.read() . Сервер отправит ответ на каждое обработанное чтение. Вам необходим аналогичный механизм на стороне клиента для обработки операций чтения. Функция, которая читает до тех пор, пока не прочитает N байтов. Вы можете отправить значение N в начале передачи данных.

Комп 3004 я вижу. Такой кошмар мы пробовали с QXmlStreamReader и QXmlStreamWriter. Писатель хорош и прост, но читатель - это кошмар, мы пытались использовать ошибку PrematureEndOfDocument в качестве точки останова, зная, что данных больше.

Другие вопросы по тегам