web-dev-qa-db-ja.com

QTcpSocket:読み書き

似たような質問がすでに出されているかもしれないことは知っていますが、私が見つけたものに対する答えは非常に具体的な問題をカバーしており、まだ理解できていません。

私のプログラムでは、QTcpSocketを使用してネットワーク経由で別のそのようなオブジェクトと通信するQObject(QPeerと呼ばれる)を作成しています。 QPeerには、データ(sendData(QByteArray))を含むQByteArrayを受け入れるスロットがあります。その配列の内容全体が1つの「メッセージ」と見なされ、ソケットに書き込まれます。次のことを行います。メッセージが書き込まれるたびに、受信側のQPeerがそのシグナルdataReceived(QByteArray)を1回だけ送信し、そのQByteArrayにメッセージ全体が含まれるようにします。 (注:QPeerをソケットに接続するプライベート信号とsendData(QByteArray)などのパブリック信号の両方のすべての信号/スロットは、必要に応じて_Qt::QueuedConnection_を使用してシリアル化されます。)

ソケットからの非同期読み取りには、信号QTcpSocket::readyRead()を使用します。これで、sendDataでQTcpSocket::write()を一度だけ呼び出すことはできず、書き込みごとに、反対側のQTcpSocketがちょうど1つの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バイト整数)をソケットに正しく書き込むにはどうすればよいですか?

2。必要なことを行うために、読み取り関数を正しく実装するにはどうすればよいですか?

どんなヒントでも大歓迎です。ありがとう!

9
DiscobarMolokai

私はあなたが期待することを行うプロジェクトに取り組みました。ここで私たちの問題に対して私が開発した解決策を見てください。

複数のクライアントを扱うサーバーのサポートを編集、追加しました。

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

注:このメソッドを使用して大きなファイルを転送しないでください。このメソッドでは、メッセージの内容全体が送信前にメモリ内に置かれ、これにより高いメモリ使用量。また、32ビットの符号付きINTの最大値は 2,147,483,647 であるため、入力データの値がバイト単位の値よりも大きい場合は機能しません。気を付けて。

37
Antonio Dias

あなたが言ったように、あなたはそれを読む前にあなたのヘッダーが完全に送信されるのを待ってから、適切なバイト数を読んで、データの可用性のためのシグナルを発する必要があります。

ここに例(未テスト):

//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