Изпращане на xml съобщение на части през TCP сокет с помощта на Qt

Ние пишем проект, в който има клиент, който генерира xml заявки, изпраща ги до сървър, който анализира заявката и връща исканата информация в xml низ.

Приложението работи добре, когато xml отговорите са малки, но когато надхвърлят около 2500 знака, понякога се прекъсват от страна на клиента. Казвам понякога, защото когато клиентът и сървърът работят на една и съща машина и комуникират през домашния адрес 127.0.0.1, отговорите се анализират добре. Въпреки това, когато клиентът и сървърът са на различни машини и комуникират през lan, това е моментът, когато клиентът съкращава съобщението до около 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 низ, разделен на части, и сървърът да прочете всички части и да го превърне отново в един низ?


person Spencer Winson    schedule 28.11.2010    source източник
comment
Това е дупка в сигурността: char buffer[1024*30] = {0}; client-›read(buffer, client-›bytesAvailable()); Може да има повече налични байтове, отколкото се побират във вашия буфер, и тогава имате препълване на буфера.   -  person Frank Osterfeld    schedule 28.11.2010


Отговори (3)


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

person Community    schedule 28.11.2010

В TCP има нещо, известно като максимален размер на сегмента. Преди инициализиране на трансфера на данни и двете страни решават MSS във фазата на ръкостискане на SYN. Това е причината вашите данни да бъдат разделени.

Имате само един client.read() . Сървърът ще изпрати отговор за всяко обработено четене. Имате нужда от подобен механизъм от страна на клиента, за да обработвате четения. Функция, която чете, докато прочете N брой байта. Можете да изпратите стойността N в началото на вашия трансфер на данни.

person i0exception    schedule 28.11.2010

COMP 3004 Виждам. Такъв кошмар, опитваме с QXmlStreamReader и QXmlStreamWriter. Писателят е хубав и прост, но четецът е кошмар, ние се опитваме да използваме грешката PrematureEndOfDocument като точка на прекъсване, за да знаем, че има повече данни.

person SomeCUGuy    schedule 01.12.2010