web-dev-qa-db-fra.com

QTcpSocket: lecture et écriture

Je sais que des questions similaires ont peut-être déjà été posées, mais les réponses à celles que j'ai trouvées couvraient des problèmes très spécifiques et je ne l'ai toujours pas compris.

Dans mon programme, je crée un QObject (appelé QPeer) qui utilise un QTcpSocket pour communiquer avec un autre de ces objets sur un réseau. QPeer a un slot qui accepte un QByteArray avec des données (sendData(QByteArray)). L'ensemble du contenu de ce tableau est considéré comme un "message" et il est écrit dans le socket. Je veux faire ce qui suit: chaque fois qu'un message est écrit, je veux que le QPeer récepteur émette son signal dataReceived(QByteArray) exactement une fois, ce QByteArray contenant le message entier. (REMARQUE: tous les signaux/emplacements, les deux privés connectant le QPeer à sa prise et les publics tels que sendData(QByteArray) sont sérialisés en utilisant Qt::QueuedConnection Chaque fois que nécessaire.)

J'utilise le signal QTcpSocket::readyRead() pour la lecture asynchrone de la prise. Maintenant, je sais que je ne peux pas simplement appeler QTcpSocket::write() une fois dans sendData et supposer que pour chaque écriture que je fais, le QTcpSocket de l'autre côté produit exactement un signal readyRead. Donc qu'est ce que je devrais faire?

C'est mon idée, dites-moi si cela fonctionnera:

L'ÉCRITURE:

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);
}

EN TRAIN DE LIRE:

maintenant, je veux que la fonction de lecture (connectée à QTcpSocket::readyRead()) utilise l'en-tête (l'int 4 octets spécifiant la longueur du message), puis lit cette quantité d'octets; ensuite, émettez dataReceived avec exactement ces octets. J'ai de la difficulté à essayer de faire ça. Par exemple: que faire si readyRead est émis et que je peux lire l'en-tête d'un message, mais pas la quantité d'octets spécifiée? Ou que se passe-t-il si un en-tête n'a été reçu que partiellement?

1. Comment puis-je écrire correctement l'en-tête (4 octets int) dans le socket?

2. Comment implémenter correctement la fonction de lecture pour qu'elle fasse ce que je veux?

Tous les conseils sont les bienvenus. Merci!

9
DiscobarMolokai

J'ai travaillé sur un projet qui fait ce que vous attendez, voyez ici la solution que j'ai développée à nos problèmes, simplifiée pour être plus facile à comprendre:

Prise en charge modifiée et ajoutée à la gestion des serveurs avec plusieurs clients.

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;
};

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;
}

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
};

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;
}

Remarque: N'utilisez pas cette méthode pour transférer des fichiers volumineux car avec cette méthode, tout le contenu du message est placé dans la mémoire avant d'être envoyé et cela provoque un utilisation élevée de la mémoire. Et comme l'INT signé 32 bits a une valeur maximale de 2 147 483 647 , si vos données d'entrée ont une valeur supérieure à celle en octets, cela ne fonctionnera pas. Prends soin de toi.

37
Antonio Dias

Comme vous l'avez dit, vous devez attendre que votre en-tête soit entièrement envoyé, avant de le lire, puis lire le bon nombre d'octets et émettre un signal pour la disponibilité des données.

Voici un exemple (non testé):

//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
       }
    }
}
2
epsilon