Отправка 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 в качестве точки останова, зная, что данных больше.