蓝牙聊天
展示了通过RFCOMM协议进行的蓝牙通信。
“蓝牙聊天”示例演示了如何使用 Qt Bluetooth API 通过蓝牙 RFCOMM 协议与远程设备上的另一个应用程序进行通信。

“蓝牙聊天”示例实现了一个多方参与的简单聊天程序。该应用程序始终同时充当服务器和客户端,从而无需确定谁应连接到谁。
运行示例
您可以从以下位置运行该示例:
- Qt Creator
打开Welcome 模式,并从Examples 中选择该示例。有关更多信息,请参阅Qt Creator :教程:构建和运行。
- Qt Extension for Visual Studio Code
在Command Palette 中运行Qt: Open Qt examples 命令,然后从列表中选择该示例。有关更多信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
聊天服务器
聊天服务器由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;
};聊天服务器首先需要创建一个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(服务发现协议)数据库中发布一条描述该服务的记录。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"));蓝牙使用UUID作为唯一标识符。聊天服务使用一个随机生成的UUID。
static constexpr auto serviceUuid = "e8e10f95-1a70-4b27-9ccf-02010264e9c8"_L1;
serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));只有当蓝牙服务位于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();
// 关闭服务器
deleterfcommServer;
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);当发现匹配的服务时,会发出一个serviceDiscovered()信号,并以QBluetoothServiceInfo 的实例作为参数。利用此服务信息提取设备名称和服务名称,并将新条目添加到已发现的远程设备列表中:
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);随后,用户可以从列表中选择其中一台设备并尝试与其建立连接。
聊天客户端
聊天客户端由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";
connect(socket, &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 类实现。该类显示单个ChatServer 与零个或多个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 已加入聊天”和“Y 已离开”消息。
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);
// 扫描服务
constQBluetoothAddress adapter=localAdapters.isEmpty()?
QBluetoothAddress() :
localAdapters.at(currentAdapterIndex).address();
RemoteSelector remoteSelector(adapter);
#ifdef Q_OS_ANDROID
// QTBUG-61392
Q_UNUSED(serviceUuid);
remoteSelector.startDiscovery(QBluetoothUuid(reverseUuid));
#else
remoteSelector.startDiscovery(QBluetoothUuid(serviceUuid));
#endif
if(remoteSelector.exec()==QDialog::Accepted) {
QBluetoothServiceInfo service=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() 信号时,我们会在聊天会话中显示“已加入与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();
}
}© 2026 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.