Qt/C++によるTCPネットワークデバッガの設計と実装

TCPネットワークデバッガの開発

本プロジェクトでは、UIの簡素化を図りつつ、TCP通信の核心技術に焦点を当てたネットワークデバッガを開発する。主な目的は、クライアント・サーバー間の接続確立、データ送受信の実装であり、同時にQtのGUIコンポーネントの再利用も行う。

サーバー側の実装フロー

QtでTCPサーバーを構築するには、QTcpServer クラスを使用し、以下の手順を踏む:

  1. サーバーの初期化QTcpServer をインスタンス化し、指定されたポートでクライアントの接続を待機(listen)させる。
  2. 新規接続の処理newConnection シグナルをスロットに接続し、nextPendingConnection() を通じて新しいQTcpSocketを取得する。
  3. データの受信:<クライアントソケットのreadyReadシグナルをハンドリングし、readAll()でデータを読み取る。
  4. データの送信write()メソッドでクライアントへ応答を送信する。
  5. 切断処理disconnectedシグナルを監視し、接続終了時の後片付けを行う。
#include <QTcpServer>
#include <QTcpSocket>

class TcpServerHandler : public QObject {
    Q_OBJECT
public:
    explicit TcpServerHandler(QObject *parent = nullptr) : QObject(parent) {
        server = new QTcpServer(this);
        connect(server, &QTcpServer::newConnection, this, &TcpServerHandler::handleNewConnection);
        if (!server->listen(QHostAddress::Any, 8080)) {
            qDebug() << "サーバー起動失敗";
        }
    }

private slots:
    void handleNewConnection() {
        QTcpSocket *client = server->nextPendingConnection();
        connect(client, &QTcpSocket::readyRead, this, &TcpServerHandler::readClientData);
        connect(client, &QTcpSocket::disconnected, client, &QTcpSocket::deleteLater);
        qDebug() << "クライアント接続:" << client->peerAddress().toString();
    }

    void readClientData() {
        QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
        QByteArray data = client->readAll();
        qDebug() << "受信データ:" << data;
        // エコーバック
        client->write("受信しました: " + data);
    }

private:
    QTcpServer *server;
};

クライアント側の実装フロー

クライアントはQTcpSocketを使用してサーバーに接続し、双方向通信を実現する。

  1. ソケットの作成QTcpSocketを生成。
  2. サーバーへの接続connectToHost()でIPアドレスとポートを指定して接続試行。
  3. 接続状態の監視connectedおよびerrorシグナルを捕捉し、成功/失敗を判定。
  4. データ送信write()でサーバーにメッセージを送信。
  5. データ受信readyReadでレスポンスを受信。
class TcpClientHandler : public QObject {
    Q_OBJECT
public:
    explicit TcpClientHandler(QObject *parent = nullptr) : QObject(parent) {
        socket = new QTcpSocket(this);
        connect(socket, &QTcpSocket::connected, this, &TcpClientHandler::onConnected);
        connect(socket, &QTcpSocket::readyRead, this, &TcpClientHandler::onReadyRead);
        connect(socket, &QAbstractSocket::errorOccurred, this, &TcpClientHandler::onError);
        
        // 接続タイマー(タイムアウト対策)
        timeoutTimer = new QTimer(this);
        timeoutTimer->setSingleShot(true);
        timeoutTimer->setInterval(5000);
        connect(timeoutTimer, &QTimer::timeout, this, &TcpClientHandler::onConnectTimeout);

        socket->connectToHost("127.0.0.1", 8080);
        timeoutTimer->start();
    }

private slots:
    void onConnected() {
        timeoutTimer->stop();
        qDebug() << "サーバーに接続成功";
        socket->write("Hello Server!");
    }

    void onReadyRead() {
        QByteArray data = socket->readAll();
        qDebug() << "サーバーからの応答:" << data;
    }

    void onError() {
        if (timeoutTimer->isActive()) return; // タイムアウト優先
        qDebug() << "接続エラー:" << socket->errorString();
    }

    void onConnectTimeout() {
        socket->abort();
        qDebug() << "接続タイムアウト";
    }

private:
    QTcpSocket *socket;
    QTimer *timeoutTimer;
};

UI機能の拡張

ユーザーインターフェースには以下のような拡張機能を実装:

動的IPアドレス検出

現在のネットワークインターフェースからIPv4アドレスを自動取得し、コンボボックスに追加する。

void populateIpAddresses() {
    QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
    for (const QHostAddress &addr : addresses) {
        if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
            ui->comboBox_ip->addItem(addr.toString());
        }
    }
}

テキスト表示の色分け

受信ログに色をつけることで、送信元や種類を視覚的に識別可能にする。

void appendColoredText(const QString &text, QColor color) {
    QTextCursor cursor = ui->textEdit_log->textCursor();
    QTextCharFormat format;
    format.setForeground(color);
    cursor.mergeCharFormat(format);
    cursor.insertText(text + "\n");
    ui->textEdit_log->ensureCursorVisible();
}

複数クライアントへの個別送信

サーバーは接続中のすべてのクライアントをリスト管理し、コンボボックスから選択した特定のクライアントにのみデータを送信できるようにする。

void sendToSelectedClient(const QString &message) {
    QString targetPort = ui->comboBox_clients->currentText();
    QList<QTcpSocket *> clients = server->findChildren<QTcpSocket *>();

    for (QTcpSocket *client : clients) {
        if (targetPort == "全員" || QString::number(client->peerPort()) == targetPort) {
            client->write(message.toUtf8());
        }
    }
}

通信の信頼性に関する理論知識

TCP(Transmission Control Protocol)は、接続指向かつ信頼性のあるトランスポートプロトコルである。主な特性として以下がある:

  • 接続確立(3ウェイハンドシェイク):SYN → SYN-ACK → ACK の流れで接続を確立。
  • 順序保証:パケットが到着順に関係なく、正しい順序でアプリケーションに提供される。
  • 再送制御:ACKがなければパケットを再送。
  • フロー制御:ウィンドウサイズにより送信速度を調整。
  • 接続終了(4ウェイハンドシェイク):FINとACKのやり取りで安全に切断。

まとめ

本プロジェクトを通じて、QtにおけるTCP通信の基本構造、信号・スロット機構の活用、GUIとの連携方法を体系的に学習できた。特に、非同期通信におけるエラーハンドリングやUIスレッドとの整合性確保が重要なポイントである。

タグ: Qt C++ TCP QTcpSocket QTcpServer

5月17日 05:50 投稿