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リファレンス)

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

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


以上です。

2012年2月20日月曜日

( Qt C++ )QNetworkAccessManagerを使用する


はいそれでは表題の件やっていきます。
本当はQHttpをやる予定だったのですが、リファレンスを見てもらうとわかるとおり、QHttpではなく、QNetworkAccessManagerの使用を推奨しています。ですのでQHttpをやめて表題の件をやっていきます。
QHttpについてはC++ GUI Programming with Qt4(Nokia無料pdf) 320ページを見るなり、リファレンスを見るなりしてみてください。

QNetworkAccessManagerはざっくりいうとアプリケーションでネットワークへ要求を送信したり、返信を受け取ったりする時に使用するクラスです。

サンプルは独自のものを使用します。

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

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

ではコードを

(mainwindow.h)
#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QFile>
#include <QMessageBox>
#include <QTextCodec>

namespace Ui {
class MainWindow;
}

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

private slots:
    void replyFinished(QNetworkReply* reply);
private:
    Ui::MainWindow *ui;//uiにはGUI部品に関する記述
    QNetworkAccessManager *manager;
    QFile file;
};

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

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//uiにはGUI部品に関する記述
{
    ui->setupUi(this);//uiのGUI部品類初期化

    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());

    manager = new QNetworkAccessManager(this);

    connect(manager, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(replyFinished(QNetworkReply*)));

    file.setFileName("C:\\Users\\windows7\\Desktop\\test.html");
    if (!file.open(QIODevice::WriteOnly))
        return;


    manager->get(QNetworkRequest(QUrl("http://developer.qt.nokia.com/doc/qt-4.8/qnetworkaccessmanager.html")));
}

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

void MainWindow::replyFinished(QNetworkReply *reply)
{
    QString str;
    if(reply->error() == QNetworkReply::NoError)
    {
        str = tr("ダウンロード正常終了");
        file.write(reply->readAll());
        file.close();
    }
    else
    {
        str = reply->errorString();
        str +=  tr( "    ダウンロード異常終了" );
        file.close();
    }
    QMessageBox msgBox(this);
    msgBox.setText(str);
    msgBox.exec();
}
はい簡単ですね。流れはQtのQNetworkAccessManagerのリファレンスのhtmlを取得しているだけです。
ヘッダ部は説明を省略します。見たままですので。

.cppは説明します。

まずはコンストラクタ。
最初にtrの日本語文字化けを防ぐためコーデックを設定しています。これは各自の環境で違うと思うのでそれぞれの環境にあった設定を設定してください。
次にQNetworkAccessManager managerの終了を監視するためfinished(QNetworkReply*)を自前のreplyFinished(QNetworkReply*)にconnectで接続します。
次にファイルをオープンし、managerのgetで取得を開始します。

デストラクタはそのままなので説明省略します。

replyFinishedはfinished(QNetworkReply*)シグナルへと対応させましたので終了時にこの関数が呼ばれます。
引数のQNetworkReplyには、QNetworkAccessManagerに送信されるデータとヘッダーが含まれています。
この中にエラー情報も含まれているのでまずエラーの有無を確認します。エラーならfileをcloseして終了。エラー無しならreadAllでデータのQByteArrayを取り出しそれをfileに書き込みます。
最後に成功か失敗かをメッセージボックスで表示して終了です。


実行すると以下のようになります。


以上です。