2012年2月25日土曜日

( Qt C++ )簡易TCPクライアントサーバアプリケーションを使う


投稿遅れました。すいません。今後も遅れる可能性です。
それでは表題の件やっていきます。

C++ GUI Programming with Qt4 323ページの「Writing TCP Client–Server Applications」をやっていこうと思ったのですが、このサンプルを使うのは少しややこしい上に長すぎるので、かなり簡略化したサンプルを使用していきます。

いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)

また.proファイルに QT += network を必ず追加してください。(でなければ動きません。)

ではコードを

(request.h)
#include <QObject>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>

class Request : public QObject
{
    Q_OBJECT
public:
    explicit Request(QObject *parent = 0);
    QString message;
signals:
    void done();
private slots:
    void connectToServer();
    void sendRequest();
    void updateMessage();
    void error();
    void closeConnection();

private:
    QTcpSocket tcpSocket;
    quint16 nextBlockSize;
};


(request.cpp)
#include "request.h"

Request::Request(QObject *parent) :
    QObject(parent)
{
    connect(&tcpSocket, SIGNAL(connected()), this, SLOT(sendRequest()));
    connect(&tcpSocket, SIGNAL(disconnected()),
            this, SLOT(closeConnection()));
    connect(&tcpSocket, SIGNAL(readyRead()),
            this, SLOT(updateMessage()));
    connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(error()));
}

void Request::connectToServer()
{
    tcpSocket.connectToHost(QHostAddress::LocalHost, 6178);
    nextBlockSize = 0;
}

void Request::sendRequest()
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_1);

    QString string = "Request";
    out << quint16(string.size()) <<  string;

    tcpSocket.write(block);
}

void Request::updateMessage()
{
    QDataStream in(&tcpSocket);
    in.setVersion(QDataStream::Qt_4_1);

    forever {
        if (nextBlockSize == 0) {
            if (tcpSocket.bytesAvailable() < sizeof(quint16))
                break;
            in >> nextBlockSize;
        }
        if (nextBlockSize == 0xFFFF) {
            tcpSocket.close();
            break;
        }
        if (tcpSocket.bytesAvailable() < nextBlockSize)
            break;

        in >> message;

        nextBlockSize = 0;
    }
}

void Request::closeConnection()
{
    tcpSocket.close();
    emit done();
}

void Request::error()
{
    message = tcpSocket.errorString();
    tcpSocket.close();
    emit done();
}

はいややこしいですね。このRequestクラスはTCPクライアントを担うクラスです。(変数名や関数名が変ですがサンプルですのでご容赦ください。)

ヘッダ部は見ての通りですので説明を省略します。

.cpp部は説明します。
まずコンストラクタ。わらわらとconnectが記述されています。メンバ変数のtcpSocketのシグナルを検知するために記述しています。シグナルconnectedが接続、シグナルdisconnectedが接続終了、シグナルreadyReadが受信、シグナルerrorがエラー検出、これらがそれぞれのタイミングで送られてきます。これらを検知し処理するためにconnectで接続しています。

次にconnectToServer()です。ここではconnectToHostを呼び出して接続を開始しています。
ここではLocalHostのポート6178に接続しています。

次にsendRequest()です。ここではデータを作成し送信しています。
データのフォーマットはかなり簡略化しています。
( 文字列のサイズ(quint16) + 文字列 )
として送信しています。

updateMessage()は受信したデータをメンバ変数のQString messageに格納しています。
0xFFFFが現れるとデータを全て受信したとしてtcpSocketをclose()しています。

closeConnection()はtcpSocketを閉じます。処理の終わりにemit done()で送受信の終了のシグナルを出します。(done()は送受信が終了したことを示す独自シグナルです。)

errorはエラーがあればそのエラーの種類をメンバのmessageに格納します。
処理の終わりにemit done()で送受信の終了のシグナルを出します。

これをmainwindowから呼び出します。(QTcpSocketリファレンス)

(mainwindow.h)
#include <QTcpSocket>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected   slots:
    void send();

private slots:
    void Message();

private:
    Ui::MainWindow *ui;
    QTcpServer *server;
    Request *request;
};


(mainwindow.cpp)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "request.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    server = new QTcpServer(this);
    server->listen(QHostAddress::Any, 6178);

    connect(server, SIGNAL(newConnection()), this, SLOT(send()));
    request = new Request();
    connect(ui->pushButton, SIGNAL(clicked()), request, SLOT(connectToServer()));
    connect(request, SIGNAL(done()), this, SLOT(Message()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::send()
{
    QTcpSocket *client = server->nextPendingConnection();
    connect(client, SIGNAL(disconnected()), client, SLOT(deleteLater()));

    QDataStream out(client);
    out.setVersion(QDataStream::Qt_4_1);

    QString string = "Send";
    out << quint16(string.size()) <<  string << quint16(0xFFFF);
}

void MainWindow::Message()
{
    QMessageBox msgBox(this);
    msgBox.setText(request->message);
    msgBox.exec();
}

はい簡単ですね。ヘッダ部は説明を省略します。
.cppは説明します。
まずはコンストラクタ。ここでQTcpServerの実体を作成し、listenを開始します。ポートはRequestクラスと同じ6178を開けます。connectでseverへの新しい接続を検知するように設定しています。
また、ここでRequest()の実体も作成し、ボタンクリックでサーバへ接続できるようにconnectし、さらに送受信の終了時にメッセージボックスに受信したメッセージを表示するようにconnectしています。

デストラクタは省略。

次はsend()です。これは新しい接続を検知すると呼ばれるように先ほどのコンストラクタで設定しました。クライアントからの要求データを取り出すことも出来ますが、このサンプルでは相手にデータを送るだけです。ただ単に ( サイズ + "Send" + 0xFFFF ) というフォーマットのデータを送っています。

Message()はメンバのrequestのdoneシグナルを検知して実行されるよう先ほどコンストラクタで設定しました。送受信の結果であるrequestのmessageに格納された文字列をメッセージボックスに表示します。

(QTcpServerリファレンス)

実行すると以下のようになります。
(初期画面)

(開始ボタンを押して送信開始しサーバからメッセージを受け取り表示)


以上です。