QTcpSocket:读写

Dis*_*kai 9 c++ sockets qt

我知道可能已经提出了一些类似的问题,但我找到的答案涵盖了非常具体的问题,我仍然没有想出来.

在我的程序中,我正在创建一个QObject(称为QPeer),它使用QTcpSocket通过网络与另一个这样的对象进行通信.QPeer有一个插槽,可以接受带有data(sendData(QByteArray))的QByteArray .该数组的全部内容被视为一个"消息",并将它们写入套接字.我想要执行以下操作:每次写入消息时,我都希望接收QPeer只发出dataReceived(QByteArray)一次信号,即包含整个消息的QByteArray.(注意:所有信号/插槽,都是连接QPeer及其插座的私有信号/插槽,以及必要时sendData(QByteArray)使用的序列化的公共信号/插槽Qt::QueuedConnection.)

我使用信号QTcpSocket::readyRead()从套接字异步读取.现在我知道我不能只QTcpSocket::write()在sendData中调用一次,然后假设对于我做的每次写操作,另一端的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);
}
Run Code Online (Sandbox Code Playgroud)

读:

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

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

2.如何正确实现读取功能,使其达到我想要的效果?

欢迎任何提示.谢谢!

Ant*_*ias 37

我参与了一个符合您期望的项目,在这里看到我为我们的问题开发的解决方案,简化为更容易理解:

编辑,添加了对服务器处理多个客户端的支持.

Client.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;
};
Run Code Online (Sandbox Code Playgroud)

Client.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;
}
Run Code Online (Sandbox Code Playgroud)

Server.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
};
Run Code Online (Sandbox Code Playgroud)

Server.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;
}
Run Code Online (Sandbox Code Playgroud)

注意:不要使用此方法传输大文件,因为使用此方法,邮件的全部内容在发送之前都会放入内存中,这会导致内存使用率过高.并且因为32位有符号INT的最大值为2,147,483,647,如果输入数据的值高于字节数,则无效.照顾自己.