QLocalServerを使ったプロセス間通信

複数のプログラムやプロセス間で通信したい場合はQLocalServerを使うと簡単に通信できます。

ここではサーバーとクライアントでデータをやり取りする方法を紹介します。

モジュールの追加

初めにネットワーク機能を使うためにプロジェクトファイルにnetworkモジュールを追加します。

QT       += core gui network

サーバーを作る

初めにQLocalServerを使ってサーバーを作ります。

ここではクライアントから名前を送信してもらい、それに挨拶を返すサーバーを作りたいので例として次のようなHelloServerを作りました。

HelloServer.h
#ifndef LOCALSERVER_H
#define LOCALSERVER_H

#include <QLocalServer>
#include <QLocalSocket>
#include <QDataStream>

class HelloServer : public QLocalServer
{
    Q_OBJECT
public:
    explicit HelloServer(QObject * parent = 0)
        : QLocalServer(parent)
    {
        listen("HelloServer");
            ///サーバー名を設定
        connect(this, SIGNAL(newConnection()), this, SLOT(receiveSocket()));
    }

    ~HelloServer(){}

private slots:
    void receiveSocket()
    {
        QLocalSocket * clientSocket = nextPendingConnection();
        connect(clientSocket, SIGNAL(disconnected()),
                clientSocket, SLOT(deleteLater()));

        clientSocket->waitForReadyRead();
            ///ソケットの書き込みが終わるまで待機
        
        QDataStream in(clientSocket);
        qint64 blockSize;
        in.setVersion(QDataStream::Qt_5_1);
        if(clientSocket->bytesAvailable() < sizeof(qint64))
            return;

        ///ソケットからデータを受け取る。
        in >> blockSize;
        QByteArray data;
        in >> data;
        QString name(data);

        ///クライアントに挨拶を返す。
        QByteArray block;
        QDataStream out(&block, QIODevice::WriteOnly);
        out.setVersion(QDataStream::Qt_5_1);
        out << (qint64)0;
        out << QString("Hello %1!!").arg(name).toUtf8();
        out.device()->seek(0);
        out << (qint64)(block.size() - sizeof(qint64));
        clientSocket->write(block);
        clientSocket->flush();
        clientSocket->disconnectFromServer();
    }
};

#endif // LOCALSERVER_H

ローカルサーバーとのデータのやり取りにはQLocalSocketを使います。

ここでの大まかな流れを箇条書きすると次のような感じです。

  1. サーバー名を設定

    listenメソッドに渡した文字列がサーバー名になり、クライアントはこの名前からサーバーに接続できます。

  2. QLocalSocketからconnetServerが呼ばれる

    connetServerが呼ばれるとサーバーのnewConnectionシグナルが呼ばれます。

  3. QDataStreamからデータを処理

    ソケットから送られてくるデータは直列化してあるのでデータサイズと実際のデータをそれぞれ受け取ります。

  4. クライアントにデータ送信

    受け取ったデータに応じてクライアントに挨拶を返します。

    この時もデータをサイズと本文の2つにまとめて直列化して送っています。

データを直列化すると書いていますが、これは異なる種類のデータを順番に受け取るためにこうしています。

クライアントの実装

サーバーを作ったら次にクライアントを作ります。

ここではクライアントとして入力された名前を送信するウィジェットを作りました。

ClientWidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QVBoxLayout>
#include <QLocalSocket>
#include <QPushButton>
#include <QLineEdit>
#include <QMessageBox>

class ClientWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ClientWidget(QWidget * parent = 0)
        : QWidget(parent), socket(new QLocalSocket(this)),
          sendButton(new QPushButton(tr("名前を送信"))), 
          nameEdit(new QLineEdit())
    {
        connect(sendButton, SIGNAL(clicked()), this, SLOT(sendName()));
        connect(socket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));

        QVBoxLayout * layout = new QVBoxLayout();
        layout->addWidget(nameEdit);
        layout->addWidget(sendButton);
        setLayout(layout);
        setWindowTitle(tr("クライアント"));
    }

    virtual ~ClientWidget()
    {
        delete socket; socket = 0;
    }

public slots:
    /** サーバーにデータ送信*/
    void sendName()
    {
        socket->abort();
        socket->connectToServer("HelloServer");
            ///HelloServerサーバーに接続

        QByteArray block;
        QDataStream out(&block, QIODevice::WriteOnly);
        out.setVersion(QDataStream::Qt_5_1);
        out << (qint64)0;
        out << nameEdit->text().toUtf8();
        out.device()->seek(0);
        out << (qint64)(block.size() - sizeof(qint64));
        socket->write(block);
    }
    
    /** サーバーからデータを受け取って表示*/
    void receiveMessage()
    {
        QDataStream in(socket);
        in.setVersion(QDataStream::Qt_5_1);
        if(in.atEnd())
            return;

        qint64 blockSize;
        in >> blockSize;
        QByteArray data;
        QString message;
        in >> data;
        message = QString(data);
        QMessageBox::information(this, tr("サーバー"), message);
    }

private:
    QLocalSocket * socket;
    QPushButton * sendButton;
    QLineEdit * nameEdit;
};
#endif // CLIENTWIDGET_H

クライアントなのでサーバにデータの送受信できるようにしてみました。

ここではサーバへのデータの送信に次の手順を踏んでいます。

  1. 一旦ソケットを初期化

    初期化にはabortを使います。

  2. サーバーに接続

    connectServerにサーバー名を渡すことでその名前のサーバに接続できます。

  3. データを直列化して送信

    データをサイズと本文の2つに分けてそれをQByteArrayで1つのブロックにします。

    あとはそれをソケットのwriteで書き込めばサーバで処理してくれます。

これがデータ送信の大まかな流れです。

次にサーバーからのデータの受信は次の仕組みになっています。

  1. サーバからソケットにデータが書き込まれるとreadyReadシグナルが送信される

    このタイミングにデータを読み込みます。

  2. ソケットからQDtataStreamを作る
  3. 直列化したデータを元の順番で読み込み
  4. サーバーからのメッセージをウインドウタイトルに表示

受信の手順はこんな感じになります。

実際に動かしてみる

では、実際にサーバとクライアントの両方を動かしてみます。

#include 

#include "ClientWidget.h"
#include "HelloServer.h"

int main(int argc, char * argv[])
{
    QApplication a(argc, argv);

    HelloServer server;
    ClientWidget client;
    client.show();

    return a.exec();
}

するとこんなクライアントウインドウが表示されます。

クライアントウインドウ

このウインドウで名前を入力してから「名前を送信」ボタンを押すと...

ウインドウタイトルにサーバから受け取ったメッセージが表示される

サーバから挨拶が返ってきました!

以上、QLocalServerを使ったプロセス間通信でした。では、また!

関連項目
プライバシーポリシー