フォーチュン・クライアント

ネットワーク サービスのクライアントを作成する方法を示します。

この例はQTcpSocket を使用しており、Fortune Server の例またはThreaded Fortune Server の例と一緒に実行することを想定しています。

Screenshot of the Fortune Client example

この例では、単純なQDataStream ベースのデータ転送プロトコルを使用して、(Fortune Serverの例から)fortune サーバーにテキスト行を要求します。クライアントはサーバーに接続するだけで、フォーチュンをリクエストする。そしてサーバーは、おみくじテキストを含むQString で応答する。

QTcpSocket は、ネットワーク・プログラミングに対する2つの一般的なアプローチをサポートしている:

  • 非同期(ノンブロッキング)アプローチ。操作はスケジュールされ、Qtのイベントループに制御が戻ったときに実行されます。操作が終了すると、QTcpSocket がシグナルを発します。例えば、QTcpSocket::connectToHost() はすぐに戻り、接続が確立されると、QTcpSocketconnected() を発信します。
  • 同期(ブロッキング)アプローチ。非GUIおよびマルチスレッド・アプリケーションでは、シグナルに接続する代わりに、waitFor...() 関数(例えば、QTcpSocket::waitForConnected())を呼び出して、操作が完了するまで呼び出し元のスレッドを一時停止させることができます。

この例では、非同期アプローチを示します。ブロッキング・フォーチュン・クライアントの例では、同期的なアプローチを説明します。

このクラスには、いくつかのデータといくつかのプライベート・スロットが含まれています:

class Client : public QDialog
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);

private slots:
    void requestNewFortune();
    void readFortune();
    void displayError(QAbstractSocket::SocketError socketError);
    void enableGetFortuneButton();

private:
    QComboBox *hostCombo = nullptr;
    QLineEdit *portLineEdit = nullptr;
    QLabel *statusLabel = nullptr;
    QPushButton *getFortuneButton = nullptr;

    QTcpSocket *tcpSocket = nullptr;
    QDataStream in;
    QString currentFortune;
};

GUIを構成するウィジェット以外のデータメンバーには、QTcpSocket ポインタ、ソケットを操作するQDataStream オブジェクト、現在表示されているフォーチュンテキストのコピーが含まれます。

ソケットはClientコンストラクタで初期化される。ソケットを削除する心配がないように、メインウィジェットを親として渡す:

Client::Client(QWidget *parent)
    : QDialog(parent)
    , hostCombo(new QComboBox)
    , portLineEdit(new QLineEdit)
    , getFortuneButton(new QPushButton(tr("Get Fortune")))
    , tcpSocket(new QTcpSocket(this))
{
    ...
    in.setDevice(tcpSocket);
    in.setVersion(QDataStream::Qt_6_5);

プロトコルはQDataStream に基づいているので、ストリームデバイスを新しく作成したソケットに設定します。プロトコルはQDataStream::Qt_6_5 に基づいているので、ストリームデバイスを新しく作成したソケットに設定します。次に、ストリームのプロトコルバージョンを に明示的に設定し、クライアントとサーバーがどのバージョンの Qt を使用していても、フォーチュンサーバーと同じバージョンを使用できるようにします。

この例で必要なQTcpSocket シグナルは、データが受信されたことを示すQTcpSocket::readyRead() と、接続エラーをキャッチするために使用するQTcpSocket::errorOccurred() だけです:

    ...
    connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune);
    connect(tcpSocket, &QAbstractSocket::errorOccurred,
    ...
}

Get Fortune ボタンをクリックすると、requestNewFortune() スロットが起動する:

void Client::requestNewFortune()
{
    getFortuneButton->setEnabled(false);
    tcpSocket->abort();
    tcpSocket->connectToHost(hostCombo->currentText(),
                             portLineEdit->text().toInt());
}

前の接続が終了する前にユーザがGet Fortune をクリックすることを許可しているので、まずQTcpSocket::abort() を呼び出して前の接続を中断する。(未接続のソケットでは、この関数は何もしない。) その後、QTcpSocket::connectToHost()を呼び出して、ユーザーインターフェースからホスト名とポートを引数として渡して、フォーチュンサーバーへの接続を続行する。

connectToHost()を呼び出した結果、次の2つのうちどちらかが起こる:

  • 接続が確立される。この場合、サーバーからfortuneが送られてくる。QTcpSocket は、データ・ブロックを受信するたびにreadyRead ()を発する。
  • エラーが発生する。接続に失敗したか、切断されたかをユーザーに知らせる必要がある。この場合、QTcpSocketerrorOccurred ()を発し、Client::displayError() が呼び出される。

まず、errorOccurred ()の場合を見てみよう:

void Client::displayError(QAbstractSocket::SocketError socketError)
{
    switch (socketError) {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The host was not found. Please check the "
                                    "host name and port settings."));
        break;
    case QAbstractSocket::ConnectionRefusedError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The connection was refused by the peer. "
                                    "Make sure the fortune server is running, "
                                    "and check that the host name and port "
                                    "settings are correct."));
        break;
    default:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The following error occurred: %1.")
                                 .arg(tcpSocket->errorString()));
    }

    getFortuneButton->setEnabled(true);
}

QMessageBox::information() を使用して、すべてのエラーをダイアログにポップアップします。QTcpSocket::RemoteHostClosedErrorは黙って無視されます。なぜなら、フォーチュン・サーバー・プロトコルはサーバーが接続を閉じることで終了するからです。

次に、readyRead() を使用します。このシグナルはClient::readFortune() に接続されています:

void Client::readFortune()
{
    in.startTransaction();

    QString nextFortune;
    in >> nextFortune;

    if (!in.commitTransaction())
        return;

    if (nextFortune == currentFortune) {
        QTimer::singleShot(0, this, &Client::requestNewFortune);
        return;
    }

    currentFortune = nextFortune;
    statusLabel->setText(currentFortune);
    getFortuneButton->setEnabled(true);
}

さて、TCPはデータのストリームを送信することを基本としているため、fortune全体を一度に取得することは期待できません。特に低速のネットワークでは、データがいくつかの小さな断片に分かれて受信されることがある。QTcpSocket はすべての受信データをバッファリングし、新しいブロックが到着するたびにreadyRead() を発する。パージングを開始する前に、必要なデータをすべて受信したことを確認するのが我々の仕事である。

この目的のために、QDataStream の読み取りトランザクションを使用する。これはストリーム・データを内部バッファに読み込み続け、読み込みが不完全な場合はロールバックする。まず startTransaction() を呼び出す。このとき、新しいデータをソケットで受信したことを示すために、ストリームのステータスもリセットする。QString QDataStream::commitTransaction にソケットからフォーチュンを読み込むために、QDataStream のストリーミング演算子を使用して処理を進める。完全なパケットを受信していない場合、この関数はストリームデータを初期位置に復元し、新しい readyRead()シグナルを待つ。

読み取りトランザクションが成功したら、QLabel::setText ()を呼び出しておみくじを表示する。

プロジェクト例 @ code.qt.io

Fortune ServerおよびBlocking Fortune Clientも参照してください

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundationによって発行されたGNU Free Documentation License version 1.3の条件の下でライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。