Shows communication through Bluetooth using RFCOMM protocol.
蓝牙聊天范例展示如何使用 Qt Bluetooth API to communicate with another application on a remote device using Bluetooth RFCOMM protocol.
The Bluetooth Chat example implements a simple chat program between multiple parties. The application always acts as both a server and a client eliminating the need to determine who should connect to whom.
要运行范例从 Qt Creator ,打开 欢迎 模式,然后选择范例从 范例 。更多信息,拜访 构建和运行范例 .
The chat server is implemented by the
ChatServer
类。
ChatServer
class is declared as:
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; };
The first thing the chat server needs to do is create an instance of
QBluetoothServer
to listen for incoming Bluetooth connections. The
clientConnected()
slot will be called whenever a new connection is created.
rfcommServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this); connect(rfcommServer, &QBluetoothServer::newConnection, this, QOverload<>::of(&ChatServer::clientConnected)); bool result = rfcommServer->listen(localAdapter); if (!result) { qWarning() << "Cannot bind chat server to" << localAdapter.toString(); return; }
The chat server is only useful if others know that it is there. To enable other devices to discover it, a record describing the service needs to be published in the system's SDP (Service Discovery Protocol) database. The QBluetoothServiceInfo class encapsulates a service record.
We will publish a service record that contains some textual descriptions of the services, a UUID that uniquely identifies the service, the discoverability attribute, and connection parameters.
The textual description of the service is stored in the
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 uses UUIDs as unique identifiers. The chat service uses a randomly generated UUID.
static constexpr auto serviceUuid = "e8e10f95-1a70-4b27-9ccf-02010264e9c8"_L1; serviceInfo.setServiceUuid(QBluetoothUuid(serviceUuid));
A Bluetooth service is only discoverable if it is in the PublicBrowseGroup .
const auto groupUuid = QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::PublicBrowseGroup); QBluetoothServiceInfo::Sequence publicBrowse; publicBrowse << QVariant::fromValue(groupUuid); serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList, publicBrowse);
The
ProtocolDescriptorList
attribute is used to publish the connection parameters that the remote device requires to connect to our service. Here we specify that the
Rfcomm
protocol is used and set the port number to the port that our
rfcommServer
instance is listening to.
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);
Finally, we register the service record with the system.
serviceInfo.registerService(localAdapter);
As mentioned earlier, incoming connections are handled in the
clientConnected()
slot where pending connections are connected to the
readyRead
() 和
disconnected
() signals. The signals notify others that a new client has connected.
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()); }
The
readSocket()
slot is called whenever data is ready to be read from a client socket. The slot reads individual lines from the socket, converts them from UTF-8, and emits the
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())); } }
The
clientDisconnected()
slot is called whenever a client disconnects from the service. The slot emits a signal to notify others that a client has disconnected, and deletes the socket.
void ChatServer::clientDisconnected() { QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender()); if (!socket) return; emit clientDisconnected(clientNames[socket]); clientSockets.removeOne(socket); clientNames.remove(socket); socket->deleteLater(); }
The
sendMessage()
slot is used to send a message to all connected clients. The message is converted into UTF-8 and appended with a newline before being sent to all clients.
void ChatServer::sendMessage(const QString &message) { QByteArray text = message.toUtf8() + '\n'; for (QBluetoothSocket *socket : std::as_const(clientSockets)) socket->write(text); }
When the chat server is stopped, the service record is removed from the system SDP database, all connected client sockets are deleted, and the
rfcommServer
instance is deleted.
void ChatServer::stopServer() { // Unregister service serviceInfo.unregisterService(); // Close sockets qDeleteAll(clientSockets); clientNames.clear(); // Close server delete rfcommServer; rfcommServer = nullptr; }
Before connecting to the server, the client needs to scan the nearby devices and search for the device that is advertising the chat service. This is done by the
RemoteSelector
类。
To start service lookup, the
RemoteSelector
创建实例为
QBluetoothServiceDiscoveryAgent
and connects to its signals.
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);
An UUID filter is set, so that the service discovery only shows the devices that advertise the needed service. After that a FullDiscovery is started:
m_discoveryAgent->setUuidFilter(uuid); m_discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
When a matching service is discovered, a serviceDiscovered () signal is emitted with an instance of QBluetoothServiceInfo as a parameter. This service info is used to extract the device name and the service name, and add a new entry to the list of discovered remote devices:
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);
Later the user can select one of the devices from the list and try to connect to it.
The chat client is implemented by the
ChatClient
类。
ChatClient
class is declared as:
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; };
The client creates a new
QBluetoothSocket
and connects to the remote service described by the
remoteService
parameter. Slots are connected to the socket's
readyRead
(),
connected
(),和
disconnected
() 信号。
void ChatClient::startClient(const QBluetoothServiceInfo &remoteService) { if (socket) return; // Connect to service socket = new QBluetoothSocket(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); }
On successful socket connection we emit a signal to notify other users.
void ChatClient::connected() { emit connected(socket->peerName()); }
Similarly to the chat server, the
readSocket()
slot is called when data is available from the socket. Lines are read individually and converted from UTF-8. The
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())); } }
The
sendMessage()
slot is used to send a message to the remote device. The message is converted to UTF-8 and a newline is appended.
void ChatClient::sendMessage(const QString &message) { QByteArray text = message.toUtf8() + '\n'; socket->write(text); }
To disconnect from the remote chat service, the QBluetoothSocket instance is deleted.
void ChatClient::stopClient() { delete socket; socket = nullptr; }
The main window of this example is the chat dialog, implemented in the
聊天
class. This class displays a chat session between a single
ChatServer
and zero or more
ChatClient
s. The
聊天
class is declared as:
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; };
First we construct the user interface
ui->setupUi(this); connect(ui->connectButton, &QPushButton::clicked, this, &Chat::connectClicked); connect(ui->sendButton, &QPushButton::clicked, this, &Chat::sendClicked);
We create an instance of the
ChatServer
and respond to its
clientConnected()
,
clientDiconnected()
,和
messageReceived()
signals.
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();
In response to the
clientConnected()
and
clientDisconnected()
signals of the
ChatServer
, we display the typical "X has joined chat." and "Y has left." messages in the chat session.
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)); }
Incoming messages from clients connected to the
ChatServer
are handled in the
showMessage()
slot. The message text tagged with the remote device name is displayed in the chat session.
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(); }
In response to the connect button being clicked, the application starts service discovery and presents a list of discovered chat services on remote devices. A
ChatClient
for the service is selected by the user.
void Chat::connectClicked() { ui->connectButton->setEnabled(false); // scan for services const QBluetoothAddress 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(); // Create client ChatClient *client = new ChatClient(this); connect(client, &ChatClient::messageReceived, this, &Chat::showMessage); connect(client, &ChatClient::disconnected, this, QOverload<>::of(&Chat::clientDisconnected)); connect(client, QOverload<const QString &>::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); }
In reponse to the
connected()
signals from
ChatClient
, we display the "Joined chat with X." message in the chat session.
void Chat::connected(const QString &name) { ui->chat->insertPlainText(QString::fromLatin1("Joined chat with %1.\n").arg(name)); }
Messages are sent to all remote devices via the
ChatServer
and
ChatClient
instances by emitting the
sendMessage()
信号。
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 }
We need to clean up
ChatClient
instances when the remote device forces a disconnect.
void Chat::clientDisconnected() { ChatClient *client = qobject_cast<ChatClient *>(sender()); if (client) { clients.removeOne(client); client->deleteLater(); } }