【问题标题】:QTcpSocket: reading and writingQTcpSocket:读写
【发布时间】:2013-12-31 01:20:33
【问题描述】:

我知道可能已经有人问过一些类似的问题,但我找到的答案涵盖了非常具体的问题,我仍然没有弄清楚。

在我的程序中,我正在创建一个 QObject(称为 QPeer),它使用 QTcpSocket 通过网络与另一个此类对象进行通信。 QPeer 有一个插槽,可以接受带有数据的 QByteArray (sendData(QByteArray))。该数组的全部内容被认为是一个“消息”,它们被写入套接字。我想做以下事情:每次写入消息时,我希望接收 QPeer 只发出一次信号dataReceived(QByteArray),即包含整个消息的 QByteArray。 (注意:所有信号/插槽,无论是连接 QPeer 与其套接字的私有信号/插槽还是公共信号/插槽,例如 sendData(QByteArray),都在必要时使用 Qt::QueuedConnection 进行序列化。)

我使用信号QTcpSocket::readyRead() 从套接字异步读取。现在我知道我不能只在 sendData 中调用一次QTcpSocket::write(),然后假设对于我所做的每次写入,另一侧的 QTcpSocket 都会产生一个 readyRead 信号。那我该怎么办?

这是我的想法,请告诉我这是否可行:

写作:

void QPeer::sendData(QByteArray data)
{
    // TODO: write data.size() as raw int of exactly 4 bytes to socket
    const char *bytes = data.constData();
    int bytesWritten = 0;
    while (bytesWritten < data.size())
        bytesWritten += _socket->write(bytes + bytesWritten);
}

阅读:

现在我希望读取函数(连接到QTcpSocket::readyRead())使用标头(指定消息长度的 4 字节 int),然后读取该字节数;接下来发出带有这些字节的 dataReceived 。我在尝试这样做时遇到了严重的麻烦。 例如:如果发出了 readyRead 并且我可以读取消息的标题,但不能读取指定的字节数,该怎么办?或者如果只收到部分标头怎么办?

1.如何正确地将标头(4 字节 int)写入套接字?

2。如何正确实现读取功能,以便它执行我想要的操作?

欢迎任何提示。谢谢!

【问题讨论】:

    标签: c++ qt sockets


    【解决方案1】:

    正如您所说,您需要等待您的标头完全发送,然后再读取它,然后读取合适的字节数并发出数据可用性信号。

    这里是一个例子(未经测试):

    //header file
    
    class Peer {
    //[...]
    protected:
       bool m_headerRead; //initialize to false
       unsigned int m_size_of_data_to_read;
    //[...]
    };
    
    //source file
    void QPeer::sendData(QByteArray data)
    {
      int size = data.size();
      _socket->write((const char*) &size, sizeof(int);
      //use directly QIODevice::write(QByteArray)
      _socket->write(data);
    }
    
    void QPeer::readData()
    {
        int bytes = _socket->bytesAvailable();
        bool contains_enough_data = true;
    
        while (contains_enough_data) {
           if (! m_headerRead && _socket->bytesAvailable() >= sizeof(int)) {
             //read header only and update m_size_of_data_to_read
             m_headerRead = true;
            } else if (m_headerRead && _socket->bytesAvailable >= m_size_of_data_to_read) {
              //read data here
              m_headerRead = false;
              emit dataAvailable();
           } else {
               contains_enough_data = false; //wait that further data arrived
           }
        }
    }
    

    【讨论】:

    • 所以你是说_socket-&gt;write(data) 保证整个 QByteArray 被一次写入?因为 Qt 文档是这样说的: qint64 QIODevice::write(const QByteArray & byteArray) 这是一个重载函数。将 byteArray 的内容写入设备。返回实际写入的字节数,如果发生错误,则返回 -1。 所以它表示它返回实际写入的字节数。如果整个数组总是一次写入,为什么要这样做?
    • 好点,当我有QByteArray 写入并检查 -1 结果的错误时,我总是使用此方法。你以前的做法还不错,你可以放心地用这个替换它。我会搜索 QByteArray 是否总是完全写入。
    • 我不检查 -1 错误;但我已将套接字的错误信号连接到 QPeer 中处理错误的插槽。这够了吗?谢谢您的帮助!您的实现运行良好,我尚未对其进行彻底测试,但到目前为止还不错,谢谢。
    • 我认为您不会有此类错误的错误信号。该信号更多地与独立错误(例如 DNS 故障、物理断开等)相关,请参阅AbstractSocket::Error。处理 -1 返回代码可能更安全;)
    【解决方案2】:

    我从事的项目符合您的期望,请在此处查看我针对我们的问题开发的解决方案,简化后更易于理解:

    已编辑,增加了对服务器处理多个客户端的支持。

    客户端.h:

    #include <QtCore>
    #include <QtNetwork>
    
    class Client : public QObject
    {
        Q_OBJECT
    public:
        explicit Client(QObject *parent = 0);
    
    public slots:
        bool connectToHost(QString host);
        bool writeData(QByteArray data);
    
    private:
        QTcpSocket *socket;
    };
    

    客户端.cpp:

    #include "client.h"
    
    static inline QByteArray IntToArray(qint32 source);
    
    Client::Client(QObject *parent) : QObject(parent)
    {
        socket = new QTcpSocket(this);
    }
    
    bool Client::connectToHost(QString host)
    {
        socket->connectToHost(host, 1024);
        return socket->waitForConnected();
    }
    
    bool Client::writeData(QByteArray data)
    {
        if(socket->state() == QAbstractSocket::ConnectedState)
        {
            socket->write(IntToArray(data.size())); //write size of data
            socket->write(data); //write the data itself
            return socket->waitForBytesWritten();
        }
        else
            return false;
    }
    
    QByteArray IntToArray(qint32 source) //Use qint32 to ensure that the number have 4 bytes
    {
        //Avoid use of cast, this is the Qt way to serialize objects
        QByteArray temp;
        QDataStream data(&temp, QIODevice::ReadWrite);
        data << source;
        return temp;
    }
    

    服务器.h:

    #include <QtCore>
    #include <QtNetwork>
    
    class Server : public QObject
    {
        Q_OBJECT
    public:
        explicit Server(QObject *parent = 0);
    
    signals:
        void dataReceived(QByteArray);
    
    private slots:
        void newConnection();
        void disconnected();
        void readyRead();
    
    private:
        QTcpServer *server;
        QHash<QTcpSocket*, QByteArray*> buffers; //We need a buffer to store data until block has completely received
        QHash<QTcpSocket*, qint32*> sizes; //We need to store the size to verify if a block has received completely
    };
    

    服务器.cpp:

    #include "server.h"
    
    static inline qint32 ArrayToInt(QByteArray source);
    
    Server::Server(QObject *parent) : QObject(parent)
    {
        server = new QTcpServer(this);
        connect(server, SIGNAL(newConnection()), SLOT(newConnection()));
        qDebug() << "Listening:" << server->listen(QHostAddress::Any, 1024);
    }
    
    void Server::newConnection()
    {
        while (server->hasPendingConnections())
        {
            QTcpSocket *socket = server->nextPendingConnection();
            connect(socket, SIGNAL(readyRead()), SLOT(readyRead()));
            connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
            QByteArray *buffer = new QByteArray();
            qint32 *s = new qint32(0);
            buffers.insert(socket, buffer);
            sizes.insert(socket, s);
        }
    }
    
    void Server::disconnected()
    {
        QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
        QByteArray *buffer = buffers.value(socket);
        qint32 *s = sizes.value(socket);
        socket->deleteLater();
        delete buffer;
        delete s;
    }
    
    void Server::readyRead()
    {
        QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
        QByteArray *buffer = buffers.value(socket);
        qint32 *s = sizes.value(socket);
        qint32 size = *s;
        while (socket->bytesAvailable() > 0)
        {
            buffer->append(socket->readAll());
            while ((size == 0 && buffer->size() >= 4) || (size > 0 && buffer->size() >= size)) //While can process data, process it
            {
                if (size == 0 && buffer->size() >= 4) //if size of data has received completely, then store it on our global variable
                {
                    size = ArrayToInt(buffer->mid(0, 4));
                    *s = size;
                    buffer->remove(0, 4);
                }
                if (size > 0 && buffer->size() >= size) // If data has received completely, then emit our SIGNAL with the data
                {
                    QByteArray data = buffer->mid(0, size);
                    buffer->remove(0, size);
                    size = 0;
                    *s = size;
                    emit dataReceived(data);
                }
            }
        }
    }
    
    qint32 ArrayToInt(QByteArray source)
    {
        qint32 temp;
        QDataStream data(&source, QIODevice::ReadWrite);
        data >> temp;
        return temp;
    }
    

    注意:不要使用此方法传输大文件,因为使用此方法,消息的全部内容在发送前都放在内存中,这会导致内存使用率很高。并且由于 32 位有符号 INT 的最大值为 2,147,483,647,因此如果您的输入数据的值高于以字节为单位的值,它将无法工作。保重。

    【讨论】:

    • 非常感谢!以前的答案有效,但这让我重新考虑了我的班级设计。这一切都变得有点……讨厌。
    • 一个快速的问题,不过:当你的服务器获得它的第一个客户端时(第一次调用 newConnection),套接字成员包含一个指向该连接的套接字的指针。但是如果过了一会儿又遇到一个新的传入连接,并且再次调用 newConnection 怎么办。那么之前的socket就丢失了,对吧?或者信号/插槽机制仍然可以在那个套接字上工作吗?
    • 信号会被发射,因为套接字是一个指针,它不会在作用域结束时被销毁,除非你调用delete或者使用智能指针,但是你不能更长的时间从该连接接收数据,因为 readyRead() 方法仅适用于存储在我们的变量(最后连接的客户端)上的指针要使用多个客户端,您需要创建一个包含您自己的套接字指针和相应的类的多个实例缓冲区。
    • 这是处理多个客户的方法之一。
    • 我已经编辑了答案以添加对多个客户端的支持,而无需创建类的多个实例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 2020-11-23
    相关资源
    最近更新 更多