使用 Qt 通过 TCP 套接字部分发送 xml 消息

Posted

技术标签:

【中文标题】使用 Qt 通过 TCP 套接字部分发送 xml 消息【英文标题】:Sending an xml message in parts through a TCP socket using Qt 【发布时间】:2010-11-28 00:18:41 【问题描述】:

我们正在编写一个项目,其中有一个客户端生成 xml 请求,将它们发送到服务器,服务器解析请求并以 xml 字符串返回请求的信息。

当 xml 回复很小时,应用程序可以正常工作,但是当它们超过大约 2500 个字符时,它们有时会在客户端被截断。我有时会说,因为当客户端和服务器在同一台机器上运行并通过家庭地址 127.0.0.1 进行通信时,回复被解析得很好。但是,当客户端和服务器在不同的机器上并通过局域网进行通信时,客户端会将消息削减到大约 2500 个字符。

通信是通过 tcp 套接字完成的。我们使用的是 Qt,客户端有一个 qTCPsocket,服务器有一个 qTCPserver 和一个指向 qtcpsocket 的指针。

我们认为解决我们问题的一种可能方法是将 xml 分段发送,按字符数或标签分隔。虽然我们很容易将消息分成几部分,但发送这些部分并让客户端或服务器读取并将这些部分编译成一个 xml 请求会给我们带来麻烦。

为了举例,我们想测试让客户端分多个部分发送请求。

这是我们发送请求的客户端函数调用。 xmlReq 在 else where 生成并传入。作为将消息分成几部分的示例,我们从 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 字符串,分成几部分,然后让服务器读取所有部分并再次将其变成一个字符串?

【问题讨论】:

这是一个安全漏洞:“char buffer[1024*30] = 0; client->read(buffer, client->bytesAvailable());”可用的字节数可能多于缓冲区的容量,然后缓冲区溢出。 【参考方案1】:

在 TCP 中,有一个称为最大段大小的东西。在初始化数据传输之前,双方在 SYN 握手阶段决定 MSS。这就是您的数据被拆分的原因。

你只有一个 client.read() 。服务器将为每个处理的读取发送回复。您需要在客户端使用类似的机制来处理读取。读取直到读取 N 个字节的函数。您可以在数据传输开始时发送值 N。

【讨论】:

【参考方案2】:

这是由于 TCP 协议的“流”性质而发生的。数据被分成许多数据包,在您的应用程序中,您实际上只读取其中的一部分(bytesAvailable() 不一定等于其他主机发送的字节数,它只是在套接字缓冲区)。 你要做的就是在客户端和服务器之间建立一个简单的协议。例如,客户端首先发送 STX 字符,然后是 XML,然后是 ETX 字符。 当服务器看到一个 STX 字符时,它会将所有内容读入缓冲区,直到 EXT 字符为止。 其他方法 - 以网络字节顺序发送一个 4 字节整数,指示 XML 数据的大小(以字节为单位),然后发送 XML。另一台主机必须接收整数,将其转换为其本机字节序,然后将指定数量的数据从套接字读取到缓冲区。

【讨论】:

【参考方案3】:

COMP 3004 我明白了。这样的噩梦,我们一直在尝试使用 QXmlStreamReader 和 QXmlStreamWriter。作者很简单,但读者是噩梦,我们一直在尝试使用 PrematureEndOfDocument 错误作为断点,以了解还有更多数据。

【讨论】:

以上是关于使用 Qt 通过 TCP 套接字部分发送 xml 消息的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++/QT 中使用 TCP 服务器套接字创建 Http 服务器

使用 C 中的套接字通过 TCP 发送音频文件

Qt 通过 TCP 套接字实时流式传输音频

在 Qt 中通过 TCP 传输大文件

将 XML 数据从计算机发送到计算机 - Qt

QT学习笔记(13) QT下的UDP通信