ブルートゥース・チャット
RFCOMMプロトコルを使用したBluetoothによる通信を表示します。
Bluetooth チャットの例では Qt BluetoothAPI を使用して、Bluetooth RFCOMM プロトコルを使用してリモートデバイス上の別のアプリケーションと通信する方法を示します。
Bluetooth Chat の例では、複数の当事者間で簡単なチャットプログラムを実装しています。このアプリケーションは常にサーバーとクライアントの両方の役割を果たすため、誰が誰に接続するかを決定する必要はありません。
例の実行
からサンプルを実行するには Qt Creatorからサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Exampleを参照してください。
チャット・サーバー
チャット・サーバーはChatServer
クラスによって実装されています。ChatServer
:
class ChatServer : public QObject { Q_OBJECT public: explicit ChatServer(QObject *parent = nullptr); ~ChatServer(); void startServer(const QBluetoothAddress &localAdapter = QBluetoothAddress()); void stopServer(); public slots: void sendMessage(const QString &message); signals: void messageReceived(const QString &sender, const QString &message); void clientConnected(const QString &name); void clientDisconnected(const QString &name); private slots: void clientConnected(); void clientDisconnected(); void readSocket(); private: QBluetoothServer *rfcommServer = nullptr; QBluetoothServiceInfo serviceInfo; QList<QBluetoothSocket *> clientSockets; QMap<QBluetoothSocket *, QString> clientNames; };
チャット・サーバーが最初に行う必要があるのは、Bluetooth接続の着信をリッスンするためにQBluetoothServer のインスタンスを作成することです。clientConnected()
スロットは、新しい接続が作成されるたびに呼び出されます。
rfcommServer= newQBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this); connect(rfcommServer, &)QBluetoothServer::newConnection, this, QOverload<>::of(&ChatServer::clientConnected));boolresult= rfcommServer->listen(localAdapter);if(!result) { 接続が作成されます。 qWarning() << "Cannot bind chat server to" << localAdapter.toString(); return; }
チャットサーバーは、それが存在することを他のデバイスが知っている場合にのみ有用である。他のデバイスが発見できるようにするには、システムの SDP (Service Discovery Protocol) データベースにサービスを説明するレコードを公開する必要がある。QBluetoothServiceInfo クラスはサービスレコードをカプセル化する。
サービスのテキスト記述、サービスを一意に識別するUUID、発見可能属性、接続パラメータを含むサービスレコードを公開する。
サービスのテキスト記述は、ServiceName
、ServiceDescription
、ServiceProvider
属性に格納されます。
serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceName, tr("Bt Chat Server")); serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceDescription, tr("Example bluetooth chat server")); serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceProvider, tr("qt-project.org"));
BluetoothはUUIDを一意の識別子として使用します。チャット・サービスはランダムに生成されたUUIDを使用します。
static constexpr auto serviceUuid = "e8e10f95-1a70-4b27-9ccf-02010264e9c8"_L1; serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));
Bluetoothサービスは、PublicBrowseGroup 。
const auto groupUuid = QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup); QBluetoothServiceInfo::Sequence publicBrowse; publicBrowse << QVariant::fromValue(groupUuid); serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
ProtocolDescriptorList
属性は、リモートデバイスがサービスに接続するために必要な接続パラメータを公開するために使用されます。ここでは、Rfcomm
プロトコルが使用されることを指定し、ポート番号をrfcommServer
インスタンスがリッスンしているポートに設定します。
QBluetoothServiceInfo::Sequence protocolDescriptorList; QBluetoothServiceInfo::Sequence protocol; protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::L2cap)); protocolDescriptorList.append(QVariant::fromValue(protocol)); protocol.clear(); protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::ProtocolUuid::Rfcomm)) << QVariant::fromValue(quint8(rfcommServer->serverPort())); protocolDescriptorList.append(QVariant::fromValue(protocol)); serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
最後に、サービスレコードをシステムに登録します。
serviceInfo.registerService(localAdapter);
前述したように、着信接続はclientConnected()
スロットで処理され、保留中の接続はreadyRead() とdisconnected() シグナルに接続される。このシグナルは、新しいクライアントが接続したことを他のクライアントに通知する。
void ChatServer::clientConnected() { QBluetoothSocket *socket = rfcommServer->nextPendingConnection(); if (!socket) return; connect(socket, &QBluetoothSocket::readyRead, this, &ChatServer::readSocket); connect(socket, &QBluetoothSocket::disconnected, this, QOverload<>::of(&ChatServer::clientDisconnected)); clientSockets.append(socket); clientNames[socket] = socket->peerName(); emit clientConnected(socket->peerName()); }
readSocket()
スロットは、クライアントソケットからデータを読み込む準備ができたときに呼び出される。このスロットはソケットから個々の行を読み込み、UTF-8から変換し、messageReceived()
シグナルを出力する。
void ChatServer::readSocket() { QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender()); if (!socket) return; while (socket->canReadLine()) { QByteArray line = socket->readLine().trimmed(); emit messageReceived(clientNames[socket], QString::fromUtf8(line.constData(), line.length())); } }
clientDisconnected()
スロットは、クライアントがサービスを切断するたびに呼び出される。スロットはクライアントが接続を切断したことを通知するシグナルを発し、ソケットを削除する。
void ChatServer::clientDisconnected() { QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender()); if (!socket) return; emit clientDisconnected(clientNames[socket]); clientSockets.removeOne(socket); clientNames.remove(socket); socket->deleteLater(); }
sendMessage()
スロットは接続されている全てのクライアントにメッセージを送信するために使用されます。メッセージはUTF-8に変換され、すべてのクライアントに送信される前に改行が追加されます。
void ChatServer::sendMessage(const QString &message) { QByteArray text = message.toUtf8() + '\n'; for (QBluetoothSocket *socket : std::as_const(clientSockets)) socket->write(text); }
チャットサーバーが停止すると、システムのSDPデータベースからサービスレコード が削除され、接続されているすべてのクライアントソケットが削除され、rfcommServer
インスタンスが削除される。
voidChatServer::stopServer() {// サービスの登録を解除serviceInfo.unregisterService();// ソケットを閉じる qDeleteAll(clientSockets); clientNames.clear();// サーバを閉じるrfcommServer を削除するrfcommServer=nullptr; }
サービスの検出
サーバーに接続する前に、クライアントは近くのデバイスをスキャンして、チャット・サービスを宣伝しているデバイスを探す必要がある。これは、RemoteSelector
クラスによって行われます。
サービス検索を開始するために、RemoteSelector
はQBluetoothServiceDiscoveryAgent のインスタンスを作成し、そのシグナルに接続する。
m_discoveryAgent = new QBluetoothServiceDiscoveryAgent(localAdapter); connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered, this, &RemoteSelector::serviceDiscovered); connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished, this, &RemoteSelector::discoveryFinished); connect(m_discoveryAgent, &QBluetoothServiceDiscoveryAgent::canceled, this, &RemoteSelector::discoveryFinished);
UUIDフィルターが設定され、サービス・ディスカバリーは、必要なサービスをアドバタイズするデバイスのみを表示する。その後、FullDiscovery :
m_discoveryAgent->setUuidFilter(uuid); m_discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
一致するサービスが発見されると、QBluetoothServiceInfo のインスタンスをパラメータとしてserviceDiscovered() シグナルが発行される。このサービス情報は、デバイス名とサービス名を抽出し、検出されたリモート・デバイスのリストに新しいエントリーを追加するために使用される:
QString remoteName; if (serviceInfo.device().name().isEmpty()) remoteName = address.toString(); else remoteName = serviceInfo.device().name(); QListWidgetItem *item = new QListWidgetItem(QString::fromLatin1("%1 %2").arg(remoteName, serviceInfo.serviceName())); m_discoveredServices.insert(item, serviceInfo); ui->remoteDevices->addItem(item);
後でユーザーは、リストから1つのデバイスを選択し、そのデバイスへの接続を試みることができる。
チャットクライアント
チャット・クライアントはChatClient
クラスで実装されています。ChatClient
:
class ChatClient : public QObject { Q_OBJECT public: explicit ChatClient(QObject *parent = nullptr); ~ChatClient(); void startClient(const QBluetoothServiceInfo &remoteService); void stopClient(); public slots: void sendMessage(const QString &message); signals: void messageReceived(const QString &sender, const QString &message); void connected(const QString &name); void disconnected(); void socketErrorOccurred(const QString &errorString); private slots: void readSocket(); void connected(); void onSocketErrorOccurred(QBluetoothSocket::SocketError); private: QBluetoothSocket *socket = nullptr; };
クライアントは新しいQBluetoothSocket を作成し、remoteService
パラメータで指定されたリモートサービスに接続します。スロットはソケットのreadyRead(),connected(),disconnected() シグナルに接続される。
voidChatClient::startClient(constQBluetoothServiceInfo&remoteService) {if(socket)return;// サービスに接続するsocket= newQBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); qDebug() << "Create socket"; socket->connectToService(remoteService); qDebug() << "ConnectToService done"; コネクト(ソケット, &QBluetoothSocket::readyRead, this, &ChatClient::readSocket); connect(socket, &QBluetoothSocket::connected, this, QOverload<>::of(&ChatClient::connected)); connect(socket, &QBluetoothSocket::disconnected, this, &ChatClient::disconnected); connect(socket, &QBluetoothSocket::errorOccurred, this, &ChatClient::onSocketErrorOccurred); }
ソケット接続に成功すると、シグナルを発して他のユーザーに通知する。
void ChatClient::connected() { emit connected(socket->peerName()); }
チャットサーバーと同様に、readSocket()
スロットはソケットからデータが利用可能になると呼び出される。行ごとに読み込まれ、UTF-8から変換される。messageReceived()
シグナルが発信される。
void ChatClient::readSocket() { if (!socket) return; while (socket->canReadLine()) { QByteArray line = socket->readLine().trimmed(); emit messageReceived(socket->peerName(), QString::fromUtf8(line.constData(), line.length())); } }
sendMessage()
スロットはリモートデバイスにメッセージを送信するために使われる。メッセージはUTF-8に変換され、改行が追加される。
void ChatClient::sendMessage(const QString &message) { QByteArray text = message.toUtf8() + '\n'; socket->write(text); }
リモートチャットサービスから切断するには、QBluetoothSocket インスタンスを削除します。
void ChatClient::stopClient() { delete socket; socket = nullptr; }
チャットダイアログ
この例のメインウィンドウは、Chat
クラスで実装されたチャットダイアログです。このクラスは、1人のChatServer
と0人以上のChatClient
の間のチャット・セッションを表示します。Chat
クラスは次のように宣言されています:
class Chat : public QDialog { Q_OBJECT public: explicit Chat(QWidget *parent = nullptr); ~Chat(); signals: void sendMessage(const QString &message); private slots: void connectClicked(); void sendClicked(); void showMessage(const QString &sender, const QString &message); void clientConnected(const QString &name); void clientDisconnected(const QString &name); void clientDisconnected(); void connected(const QString &name); void reactOnSocketError(const QString &error); void newAdapterSelected(); void initBluetooth(); void updateIcons(Qt::ColorScheme scheme); private: int adapterFromUserSelection() const; int currentAdapterIndex = 0; Ui::Chat *ui; ChatServer *server = nullptr; QList<ChatClient *> clients; QList<QBluetoothHostInfo> localAdapters; QString localName; };
まず、ユーザー・インターフェースを構築します。
ui->setupUi(this); connect(ui->connectButton, &QPushButton::clicked, this, &Chat::connectClicked); connect(ui->sendButton, &QPushButton::clicked, this, &Chat::sendClicked);
ChatServer
のインスタンスを作成し、そのclientConnected()
、clientDiconnected()
、messageReceived()
シグナルに応答する。
server = new ChatServer(this); connect(server, QOverload<const QString &>::of(&ChatServer::clientConnected), this, &Chat::clientConnected); connect(server, QOverload<const QString &>::of(&ChatServer::clientDisconnected), this, QOverload<const QString &>::of(&Chat::clientDisconnected)); connect(server, &ChatServer::messageReceived, this, &Chat::showMessage); connect(this, &Chat::sendMessage, server, &ChatServer::sendMessage); server->startServer();
ChatServer
のclientConnected()
とclientDisconnected()
シグナルに応答して、典型的な "X has joined chat. "と "Y has left. "のメッセージをチャットセッションに表示する。
void Chat::clientConnected(const QString &name) { ui->chat->insertPlainText(QString::fromLatin1("%1 has joined chat.\n").arg(name)); } void Chat::clientDisconnected(const QString &name) { ui->chat->insertPlainText(QString::fromLatin1("%1 has left.\n").arg(name)); }
ChatServer
に接続されたクライアントからの着信メッセージは、showMessage()
スロットで処理される。リモート・デバイス名でタグ付けされたメッセージ・テキストがチャット・セッションに表示されます。
void Chat::showMessage(const QString &sender, const QString &message) { ui->chat->moveCursor(QTextCursor::End); ui->chat->insertPlainText(QString::fromLatin1("%1: %2\n").arg(sender, message)); ui->chat->ensureCursorVisible(); }
接続ボタンがクリックされると、アプリケーションはサービス検出を開始し、リモートデバイス上で検出されたチャットサービスのリストを表示します。サービスのChatClient
はユーザーによって選択されます。
voidChat::connectClicked() { ui->connectButton->setEnabled(false);// サービスのスキャン constQBluetoothAddressadapter=localAdapters.isEmpty()?QBluetoothAddress() : localAdapters.at(currentAdapterIndex).address(); RemoteSelector remoteSelector(adapter);#ifdef Q_OS_ANDROID // QTBUG-61392Q_UNUSED(serviceUuid); remoteSelector.startDiscovery(QBluetoothUuid(reverseUuid));#elseremoteSelector.startDiscovery(QBluetoothUuid(serviceUuid));#endif if(remoteSelector.exec()==(remoteSelector.exec())==(remoteSelector.exec()) QDialog::Accepted) { { service = remoteSelector.exec() == (serviceUuid); #endif QBluetoothServiceInfoservice=remoteSelector.service(); qDebug() << "Connecting to service" << service.serviceName() << "on"<<service.device().name();// クライアントの作成ChatClient*client = newChatClient(this); connect(client, &ChatClient::messageReceived, this, &Chat::showMessage); connect(client, &ChatClient::disconnected, this, QOverload<>::of(&Chat::clientDisconnected)); connect(client, QOverload<constQString&>::of(&ChatClient::connected), this, &Chat::connected); connect(client, &ChatClient::socketErrorOccurred, this, &Chat::reactOnSocketError); connect(this, &Chat::sendMessage,client, &ChatClient::sendMessage); client->startClient(service); clients.append(client); } ui->connectButton->setEnabled(true); }
ChatClient
からのconnected()
シグナルに応答して、"Joined chat with X." メッセージをチャットセッションに表示します。
void Chat::connected(const QString &name) { ui->chat->insertPlainText(QString::fromLatin1("Joined chat with %1.\n").arg(name)); }
メッセージは、sendMessage()
シグナルを発することで、ChatServer
とChatClient
インスタンス経由ですべてのリモートデバイスに送信されます。
void Chat::sendClicked() { ui->sendButton->setEnabled(false); ui->sendText->setEnabled(false); showMessage(localName, ui->sendText->text()); emit sendMessage(ui->sendText->text()); ui->sendText->clear(); ui->sendText->setEnabled(true); ui->sendButton->setEnabled(true); #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) // avoid keyboard automatically popping up again on mobile devices ui->sendButton->setFocus(); #else ui->sendText->setFocus(); #endif }
リモート・デバイスが強制的に切断した場合、ChatClient
インスタンスをクリーンアップする必要がある。
void Chat::clientDisconnected() { ChatClient *client = qobject_cast<ChatClient *>(sender()); if (client) { clients.removeOne(client); client->deleteLater(); } }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.