Fortuneクライアント

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

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

Fortune Client の例のスクリーンショット

この例では、QDataStream に基づく単純なデータ転送プロトコルを使用して、(「Fortune Server」サンプルに含まれる)Fortune サーバーから 1 行のテキストを要求します。クライアントは、サーバーに接続するだけでフォーチュンを要求します。その後、サーバーはフォーチュンのテキストを含む `QString ` を返します。

QTcpSocket ネットワークプログラミングには、主に2つのアプローチがサポートされています:

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

この例では、非同期的なアプローチを説明します。「Blocking Fortune Client」の例では、同期的なアプローチが示されています。

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

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 に基づいているため、ストリームデバイスを新しく作成したソケットに設定します。次に、クライアントとサーバーがどのバージョンのQtを使用しているかに関係なく、fortuneサーバーと同じバージョンを使用していることを確実にするために、ストリームのプロトコルバージョンを明示的にQDataStream::Qt_6_5 に設定します。

この例で必要な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()を呼び出し、fortuneサーバーへの接続を行います。

connectToHost() を呼び出した結果、次の 2 つのうちのいずれかが発生します。

  • 接続が確立される。この場合、サーバーからフォーチュンが送信されます。 QTcpSocket は、データブロックを受信するたびにreadyRead()を出力します。
  • エラーが発生する。接続に失敗した場合や接続が切断された場合は、ユーザーに通知する必要があります。この場合、QTcpSocket はerrorOccurred()を発行し、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 は黙って無視されます。これは、fortune サーバーのプロトコルでは、サーバーが接続を閉じることで終了するためです。

次に、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はデータストリームの送信に基づいているため、フォーチュンを一度にすべて取得できるとは限りません。 特にネットワーク速度が遅い場合、データはいくつかの小さな断片に分かれて受信されることがあります。QTcpSocket は受信したすべてのデータをバッファに蓄積し、新しいブロックが到着するたびにreadyRead()を発火します。したがって、解析を開始する前に必要なデータをすべて受信していることを確認するのは、私たちの役割です。

この目的のために、QDataStream のreadトランザクションを使用します。これはストリームデータを内部バッファに読み込み続け、読み取りが不完全な場合はロールバックを行います。まずstartTransaction()を呼び出します。これにより、ソケットで新しいデータが受信されたことを示すよう、ストリームのステータスがリセットされます。 続いて、QDataStream のストリーミング演算子を使用して、ソケットからQString に運勢を読み込みます。読み込みが完了したら、QDataStream::commitTransaction()を呼び出してトランザクションを完了させます。完全なパケットを受け取れなかった場合、この関数はストリームデータを初期位置に戻し、その後、新しいreadyRead()シグナルを待機できるようになります。

読み取りトランザクションが正常に完了したら、QLabel::setText() を呼び出して、運勢を表示します。

サンプルプロジェクト @ code.qt.io

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

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.