Qt ネットワークプログラミング: peerAddress() の基本と応用

2025-05-27

QAbstractSocket::peerAddress() は、Qt のネットワーク関連クラスである QAbstractSocket のメンバ関数の一つです。この関数は、接続されたピア(相手方)のアドレスを返します。

より具体的に説明すると、以下のようになります。

  • 利用場面
    • サーバーアプリケーションで、接続してきたクライアントのアドレスを特定したい場合。
    • クライアントアプリケーションで、接続先のサーバーのアドレスを確認したい場合。
    • ログ出力やアクセス制御などで、通信相手のアドレス情報を利用したい場合。
  • 戻り値の型
    QHostAddress 型のオブジェクトを返します。QHostAddress クラスは、IPv4 や IPv6 のアドレスを表現するために Qt で使用されるクラスです。
  • 役割
    確立されたネットワーク接続(TCP や UDP など)において、通信相手のコンピューターやデバイスのネットワークアドレスを取得するために使用されます。


例えば、TCP ソケット (QTcpSocket) がサーバーに接続された後、そのクライアントのアドレスを取得するには以下のように記述できます。

QTcpSocket *socket = ...; // 接続済みのソケットオブジェクト

QHostAddress peerAddress = socket->peerAddress();
QString addressString = peerAddress.toString(); // QHostAddress を文字列に変換

qDebug() << "接続されたピアのアドレス:" << addressString;

このコードでは、socket->peerAddress() を呼び出すことで、接続してきたクライアントの QHostAddress オブジェクトを取得し、さらに toString() 関数を使って人間が読める文字列形式に変換しています。

同様に、ポート番号を取得するための peerPort() という関数も QAbstractSocket クラスに存在します。これらを組み合わせることで、接続相手の IP アドレスとポート番号の両方を知ることができます。



ソケットが接続されていない状態で呼び出した場合

  • トラブルシューティング
    peerAddress() を呼び出す前に、ソケットが実際に接続されているかを確認してください。QAbstractSocket::state() 関数を使用して、現在のソケットの状態を確認できます。接続状態 (QAbstractSocket::ConnectedState) になってから peerAddress() を呼び出すようにしてください。
  • エラー
    厳密なエラーが発生するわけではありませんが、無効な QHostAddress オブジェクト(例えば、アドレスが QHostAddress::NullQHostAddress::Any になっている)が返される可能性があります。


QTcpSocket *socket = ...;

if (socket->state() == QAbstractSocket::ConnectedState) {
    QHostAddress peerAddress = socket->peerAddress();
    qDebug() << "ピアのアドレス:" << peerAddress.toString();
} else {
    qDebug() << "ソケットは接続されていません。";
}

UDP ソケットの場合の注意点

  • トラブルシューティング
    UDP ソケットの場合、peerAddress() が常に意味のあるアドレスを返すとは限りません。特定のピアと通信を行う場合は、connectToHost() のような接続関数は使用せず、writeDatagram() などの送信関数で明示的に送信先のアドレスを指定する必要があります。受信したデータグラムの送信元アドレスは、QNetworkDatagram クラスの senderAddress() 関数で取得できます。
  • 説明
    UDP はコネクションレスなプロトコルであるため、TCP ソケットのように明示的な接続確立フェーズがありません。UDP ソケットで peerAddress() を呼び出すタイミングによっては、最後に通信した相手のアドレスが返されることがあります。

IPv4/IPv6 の混在環境

  • トラブルシューティング
    返ってきた QHostAddress オブジェクトが期待する形式であるかを確認し、必要に応じて toIPv4Address()toIPv6Address() などの関数を使用して変換してください。また、アプリケーションが特定の IP バージョンのみをサポートする場合は、ソケットの作成時にアドレスファミリーを明示的に指定することを検討してください(例: QTcpSocket(QAbstractSocket::IPv4Protocol))。
  • 説明
    アプリケーションが IPv4 と IPv6 の両方に対応している場合、peerAddress() が返すアドレスの形式に注意が必要です。例えば、サーバーが IPv6 アドレスでリッスンしていて、クライアントが IPv4 アドレスで接続してきた場合、peerAddress() は IPv4 マップドアドレス(IPv6 アドレス空間に埋め込まれた IPv4 アドレス)を返すことがあります。

ネットワークインターフェースの問題

  • トラブルシューティング
    ネットワーク設定(IP アドレス、サブネットマスク、ゲートウェイなど)が正しいことを確認してください。他のネットワークアプリケーションが正常に通信できるかどうかも確認すると、問題の切り分けに役立ちます。
  • 説明
    稀なケースですが、ネットワークインターフェースの設定に問題がある場合、peerAddress() が正しいアドレスを返さない可能性があります。

ファイアウォールやネットワーク設定

  • トラブルシューティング
    ファイアウォールの設定を確認し、アプリケーションの通信が許可されていることを確認してください。ネットワーク管理者と協力して、ネットワーク設定に問題がないかを確認することも有効です。
  • 説明
    ファイアウォールやルーターの設定によって、接続が確立されていても、アプリケーションがピアのアドレスを正しく認識できない場合があります。


例1: TCP サーバーで接続してきたクライアントのアドレスを表示する

この例では、簡単な TCP サーバーを作成し、クライアントが接続してきた際にそのアドレスを表示します。

#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QHostAddress>

class MyTcpServer : public QTcpServer {
public:
    MyTcpServer(QObject *parent = nullptr) : QTcpServer(parent) {}

protected:
    void incomingConnection(qintptr socketDescriptor) override {
        QTcpSocket *clientSocket = new QTcpSocket(this);
        if (!clientSocket->setSocketDescriptor(socketDescriptor)) {
            qDebug() << "ソケットディスクリプタの設定に失敗しました:" << clientSocket->errorString();
            clientSocket->deleteLater();
            return;
        }

        connect(clientSocket, &QTcpSocket::disconnected, clientSocket, &QTcpSocket::deleteLater);
        connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
            QHostAddress peerAddress = clientSocket->peerAddress();
            quint16 peerPort = clientSocket->peerPort();
            qDebug() << "クライアントが接続しました - アドレス:" << peerAddress.toString() << "ポート:" << peerPort;
            qDebug() << "受信データ:" << clientSocket->readAll();
        });

        qDebug() << "新しいクライアントが接続を試みました。";
    }
};

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

    MyTcpServer server;
    if (!server.listen(QHostAddress::Any, 12345)) {
        qDebug() << "サーバーの起動に失敗しました:" << server.errorString();
        return 1;
    }

    qDebug() << "TCP サーバーが起動しました。ポート: 12345 でリッスン中...";

    return a.exec();
}

説明

  1. MyTcpServer クラスは QTcpServer を継承し、新しい接続を受け付けるための incomingConnection() 関数をオーバーライドしています。
  2. incomingConnection() 関数内で、新しい QTcpSocket オブジェクトが作成され、接続されたソケットディスクリプタが設定されます。
  3. readyRead() シグナルが接続され、データが到着した際に匿名関数(ラムダ式)が実行されます。
  4. この匿名関数内で clientSocket->peerAddress() を呼び出すことで、接続してきたクライアントの QHostAddress オブジェクトを取得し、toString() で文字列に変換して表示しています。
  5. 同様に clientSocket->peerPort() を呼び出すことで、クライアントのポート番号を取得しています。

例2: TCP クライアントで接続先のサーバーのアドレスを表示する

この例では、簡単な TCP クライアントを作成し、サーバーに接続後にサーバーのアドレスを表示します。

#include <QTcpSocket>
#include <QCoreApplication>
#include <QDebug>
#include <QHostAddress>

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

    QTcpSocket socket;
    socket.connectToHost(QHostAddress::LocalHost, 12345); // サーバーのアドレスとポート

    if (socket.waitForConnected(5000)) {
        QHostAddress serverAddress = socket.peerAddress();
        quint16 serverPort = socket.peerPort();
        qDebug() << "サーバーに接続しました - アドレス:" << serverAddress.toString() << "ポート:" << serverPort;

        socket.write("Hello from client!\r\n");
        socket.waitForBytesWritten();
        socket.disconnectFromHost();
    } else {
        qDebug() << "サーバーへの接続に失敗しました:" << socket.errorString();
    }

    return a.exec();
}

説明

  1. QTcpSocket オブジェクト socket が作成されます。
  2. connectToHost() 関数を使用して、指定されたアドレスとポートのサーバーに接続を試みます。
  3. waitForConnected() 関数で接続が確立されるまで待機します。
  4. 接続が成功した場合、socket.peerAddress() を呼び出すことで、接続先のサーバーの QHostAddress オブジェクトを取得し、表示しています。
  5. 同様に socket.peerPort() を呼び出すことで、サーバーのポート番号を取得しています。

例3: UDP ソケットで最後に通信したピアのアドレスを表示する

UDP はコネクションレスなプロトコルなので、peerAddress() の挙動は TCP と異なります。この例では、UDP ソケットでデータを送信した後、peerAddress() を呼び出して最後に通信したピアのアドレスを表示します。

#include <QUdpSocket>
#include <QCoreApplication>
#include <QDebug>
#include <QHostAddress>

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

    QUdpSocket senderSocket;
    QHostAddress receiverAddress("127.0.0.1"); // 送信先アドレス
    quint16 receiverPort = 54321;           // 送信先ポート
    QByteArray datagram = "Hello UDP!";

    senderSocket.writeDatagram(datagram, receiverAddress, receiverPort);
    qDebug() << "データグラムを送信しました。";

    // 注意: peerAddress() は最後に通信した相手のアドレスを返す可能性があります。
    QHostAddress peerAddress = senderSocket.peerAddress();
    quint16 peerPort = senderSocket.peerPort();
    qDebug() << "最後に通信したピアのアドレス:" << peerAddress.toString() << "ポート:" << peerPort;

    QUdpSocket receiverSocket;
    if (receiverSocket.bind(QHostAddress::AnyIPv4, receiverPort)) {
        qDebug() << "UDP 受信ソケットをバインドしました。ポート:" << receiverPort;
        if (receiverSocket.waitForReadyRead(5000)) {
            QByteArray buffer;
            buffer.resize(receiverSocket.pendingDatagramSize());
            QHostAddress senderAddr;
            quint16 senderPort;
            receiverSocket.readDatagram(buffer.data(), buffer.size(), &senderAddr, &senderPort);
            qDebug() << "受信データ:" << buffer << "送信元アドレス:" << senderAddr.toString() << "ポート:" << senderPort;
            // 受信したデータグラムの送信元アドレスは senderAddr で取得できます。
        } else {
            qDebug() << "データグラムを受信しませんでした。";
        }
    } else {
        qDebug() << "UDP 受信ソケットのバインドに失敗しました:" << receiverSocket.errorString();
    }

    return a.exec();
}
  1. 送信側の QUdpSocket (senderSocket) がデータグラムを特定のアドレスとポートに送信します。
  2. 直後に senderSocket.peerAddress() を呼び出していますが、UDP はコネクションレスなので、これは必ずしも意味のあるアドレスを返すとは限りません。多くの場合、最後に writeDatagram() で指定したアドレスが返される可能性があります。
  3. 受信側の QUdpSocket (receiverSocket) は特定のポートにバインドし、データグラムを受信します。
  4. 重要な点
    UDP で受信したデータグラムの送信元アドレスは、readDatagram() 関数の引数 (&senderAddr) を通じて取得できます。peerAddress() は、UDP ソケットのコンテキストでは、TCP ソケットほど直接的ではありません。


接続確立時の情報を保存する

特にサーバーアプリケーションの場合、クライアントが接続した際に QTcpSocket::connected() シグナルが発行されます。このシグナルに接続したスロット内で、peerAddress()peerPort() の情報を取得し、メンバー変数などに保存しておくことができます。これにより、後で必要になった際に、保存しておいたアドレス情報を利用できます。

// サーバー側のクラス定義
class MyTcpServer : public QTcpServer {
    Q_OBJECT
public:
    MyTcpServer(QObject *parent = nullptr) : QTcpServer(parent) {}

private slots:
    void handleNewConnection();
    void handleClientDisconnected();

private:
    QMap<QTcpSocket*, QHostAddress> clientAddresses;
    QMap<QTcpSocket*, quint16> clientPorts;
};

// サーバー側の実装
void MyTcpServer::incomingConnection(qintptr socketDescriptor) {
    QTcpSocket *clientSocket = new QTcpSocket(this);
    if (!clientSocket->setSocketDescriptor(socketDescriptor)) {
        qDebug() << "ソケットディスクリプタの設定に失敗しました:" << clientSocket->errorString();
        clientSocket->deleteLater();
        return;
    }

    connect(clientSocket, &QTcpSocket::connected, this, [=]() {
        clientAddresses[clientSocket] = clientSocket->peerAddress();
        clientPorts[clientSocket] = clientSocket->peerPort();
        qDebug() << "クライアントが接続しました - アドレス:" << clientAddresses[clientSocket].toString() << "ポート:" << clientPorts[clientSocket];
    });
    connect(clientSocket, &QTcpSocket::disconnected, this, &MyTcpServer::handleClientDisconnected);
    connect(clientSocket, &QTcpSocket::readyRead, this, [=]() { /* 受信処理 */ });

    qDebug() << "新しいクライアントが接続を試みました。";
}

void MyTcpServer::handleClientDisconnected() {
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (clientSocket) {
        qDebug() << "クライアントが切断しました - アドレス:" << clientAddresses.value(clientSocket).toString() << "ポート:" << clientPorts.value(clientSocket);
        clientAddresses.remove(clientSocket);
        clientPorts.remove(clientSocket);
        clientSocket->deleteLater();
    }
}

説明

  • これにより、接続中であればいつでもクライアントのアドレスとポート番号にアクセスできます。
  • クライアントが切断した際には、保存していた情報を削除しています。
  • connected() シグナルが発行された際に、peerAddress()peerPort() の情報を QMap に保存しています。

UDP ソケットで送信元アドレスを明示的に取得する

UDP の場合は peerAddress() の意味合いが異なるため、受信したデータグラムの送信元アドレスを取得する際には、QUdpSocket::readDatagram() 関数の引数を使用します。

QUdpSocket receiverSocket;
if (receiverSocket.bind(QHostAddress::AnyIPv4, 54321)) {
    connect(&receiverSocket, &QUdpSocket::readyRead, [&]() {
        while (receiverSocket->hasPendingDatagrams()) {
            QByteArray datagram;
            datagram.resize(receiverSocket->pendingDatagramSize());
            QHostAddress senderAddress;
            quint16 senderPort;
            receiverSocket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
            qDebug() << "受信データ:" << datagram << "送信元アドレス:" << senderAddress.toString() << "ポート:" << senderPort;
            // senderAddress と senderPort を利用する
        }
    });
} else {
    qDebug() << "UDP ソケットのバインドに失敗しました:" << receiverSocket->errorString();
}

説明

  • UDP 通信では、この方法が送信元の情報を得るための標準的な方法です。
  • readDatagram() 関数の第三引数と第四引数に、送信元のアドレス (senderAddress) とポート番号 (senderPort) を格納するための変数のポインタを渡すことで、データグラムの送信元情報を取得できます。

ネットワークインターフェースの情報 (QNetworkInterface)

ローカルマシンのネットワークインターフェースに関する情報を取得したい場合は、QNetworkInterface クラスを使用できます。これを使うと、自身のアドレスやネットワーク構成に関する詳細な情報を得られますが、接続されたピアのアドレスを直接取得する代替にはなりません。ただし、自身のどのインターフェースを通じて通信しているかなどを知る上で役立ちます。

#include <QNetworkInterface>
#include <QDebug>

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

    QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
    for (const QNetworkInterface &interface : interfaces) {
        qDebug() << "インターフェース名:" << interface.name();
        qDebug() << "ハードウェアアドレス:" << interface.hardwareAddress();
        QList<QNetworkAddressEntry> entries = interface.addressEntries();
        for (const QNetworkAddressEntry &entry : entries) {
            qDebug() << "  IP アドレス:" << entry.ip().toString();
            qDebug() << "  ネットマスク:" << entry.netmask().toString();
            qDebug() << "  ブロードキャストアドレス:" << entry.broadcast().toString();
        }
        qDebug() << "---";
    }

    return a.exec();
}

説明

  • 各インターフェースについて、名前、ハードウェアアドレス、IP アドレスなどの情報を取得できます。
  • QNetworkInterface::allInterfaces() で、ローカルマシンのすべてのネットワークインターフェースのリストを取得します。

プロキシ経由の接続の場合 (QNetworkProxy)

プロキシサーバーを経由して接続している場合、peerAddress() が返すアドレスは最終的な宛先サーバーのアドレスではなく、プロキシサーバーのアドレスとなることがあります。プロキシに関する情報は QNetworkProxy クラスや QAbstractSocket::proxy() 関数で取得できますが、真のピアのアドレスを知るためには、アプリケーションレベルで何らかの追加情報(例えば、HTTP ヘッダーの X-Forwarded-For など)をやり取りする必要がある場合があります。

QAbstractSocket::peerAddress() は便利な関数ですが、状況によっては代替の方法を検討する必要があります。

  • プロキシ環境では、プロキシ関連のクラスや追加のプロトコルレベルの情報が必要になることがあります。
  • ローカルなネットワーク情報を取得したい場合は QNetworkInterface を利用できます。
  • UDP では、readDatagram() の引数から送信元アドレスを取得するのが基本です。
  • TCP サーバーでは、接続確立時のシグナルを利用して情報を保存するのが有効です。