蓝牙 RFCOMM
本主题提供通用 Windows 平台 (UWP) 应用中的蓝牙 RFCOMM 的概述,以及如何发送或接收文件的示例代码。
重要的 API
重要
必须在 Package.appxmanifest 中声明“蓝牙”功能。
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
概述
Windows.Devices.Bluetooth.Rfcomm 命名空间中的 API 基于 Windows.Devices 的现有模式而构建,其中包括枚举和实例化。 数据读取和写入旨在利用 Windows.Storage.Streams 中已建立的数据流模式和对象。 服务发现协议 (SDP) 属性具有一个值和一个预期类型。 但是,某些常见设备的 SDP 属性实现错误,属性值不是预期类型。 此外,RFCOMM 的许多用法根本不需要额外的 SDP 属性。 出于上述原因,此 API 提供对未分析的 SDP 数据的访问权限,开发人员通过此类访问可以获得所需的信息。
RFCOMM API 使用服务标识符概念。 尽管服务标识符只是 128 位 GUID,但它通常也被指定为 16 位或 32 位整数。 RFCOMM API 为服务标识符提供一个包装器,以便能够将这些标识符指定并用作 128 位 GUID 以及 32 位整数,但不提供 16 位整数。 这不是 API 的问题,因为语言将自动扩至 32 位整数,而且仍可以正确生成标识符。
应用可以在后台任务中执行多步设备操作,以便即使应用移动到后台并挂起,其也能运行到完成。 这样便能实现可靠的设备维护,例如对持久性设置或固件的更改以及内容同步,而无需用户坐看进度栏。 将 DeviceServicingTrigger 用于设备维护,DeviceUseTrigger 用于内容同步。 请注意,这些后台任务限制了应用可以在后台运行的时长,而且不允许无限期操作或无限期同步。
有关详细说明 RFCOMM 操作的完整代码示例,请参阅 Github 上的蓝牙 Rfcomm 聊天示例。
以客户端的形式发送文件
发送文件时,最基本的方案是基于所需的服务连接到配对设备。 这包括以下步骤:
- 使用 RfcommDeviceService.GetDeviceSelector* 函数帮助生成高级查询语法 (AQS) 查询,该查询可用于枚举所需服务的配对设备实例。
- 选取枚举设备,创建 RfcommDeviceService,并根据需要读取 SDP 属性(使用已建立的数据帮助程序分析属性的数据)。
- 创建套接字,并使用 RfcommDeviceService.ConnectionHostName 和 RfcommDeviceService.ConnectionServiceName 属性通过 StreamSocket.ConnectAsync 方法连接到具有相应参数的远程设备服务。
- 按照已建立的数据流模式从文件中读取数据区块,并通过套接字的 StreamSocket.OutputStream 将其发送到设备。
using System;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth.Rfcomm;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.Devices.Bluetooth;
Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService _service;
Windows.Networking.Sockets.StreamSocket _socket;
async void Initialize()
{
// Enumerate devices with the object push service
var services =
await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(
RfcommDeviceService.GetDeviceSelector(
RfcommServiceId.ObexObjectPush));
if (services.Count > 0)
{
// Initialize the target Bluetooth BR device
var service = await RfcommDeviceService.FromIdAsync(services[0].Id);
bool isCompatibleVersion = await IsCompatibleVersionAsync(service);
// Check that the service meets this App's minimum requirement
if (SupportsProtection(service) && isCompatibleVersion)
{
_service = service;
// Create a socket and connect to the target
_socket = new StreamSocket();
await _socket.ConnectAsync(
_service.ConnectionHostName,
_service.ConnectionServiceName,
SocketProtectionLevel
.BluetoothEncryptionAllowNullAuthentication);
// The socket is connected. At this point the App can wait for
// the user to take some action, for example, click a button to send a
// file to the device, which could invoke the Picker and then
// send the picked file. The transfer itself would use the
// Sockets API and not the Rfcomm API, and so is omitted here for
// brevity.
}
}
}
// This App requires a connection that is encrypted but does not care about
// whether it's authenticated.
bool SupportsProtection(RfcommDeviceService service)
{
switch (service.ProtectionLevel)
{
case SocketProtectionLevel.PlainSocket:
if ((service.MaxProtectionLevel == SocketProtectionLevel
.BluetoothEncryptionWithAuthentication)
|| (service.MaxProtectionLevel == SocketProtectionLevel
.BluetoothEncryptionAllowNullAuthentication))
{
// The connection can be upgraded when opening the socket so the
// App may offer UI here to notify the user that Windows may
// prompt for a PIN exchange.
return true;
}
else
{
// The connection cannot be upgraded so an App may offer UI here
// to explain why a connection won't be made.
return false;
}
case SocketProtectionLevel.BluetoothEncryptionWithAuthentication:
return true;
case SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication:
return true;
}
return false;
}
// This App relies on CRC32 checking available in version 2.0 of the service.
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A; // UINT32
const uint MINIMUM_SERVICE_VERSION = 200;
async Task<bool> IsCompatibleVersionAsync(RfcommDeviceService service)
{
var attributes = await service.GetSdpRawAttributesAsync(
BluetoothCacheMode.Uncached);
var attribute = attributes[SERVICE_VERSION_ATTRIBUTE_ID];
var reader = DataReader.FromBuffer(attribute);
// The first byte contains the attribute's type
byte attributeType = reader.ReadByte();
if (attributeType == SERVICE_VERSION_ATTRIBUTE_TYPE)
{
// The remainder is the data
uint version = reader.ReadUInt32();
return version >= MINIMUM_SERVICE_VERSION;
}
else return false;
}
...
#include <winrt/Windows.Devices.Bluetooth.Rfcomm.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.h>
...
Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService m_service{ nullptr };
Windows::Networking::Sockets::StreamSocket m_socket;
Windows::Foundation::IAsyncAction Initialize()
{
// Enumerate devices with the object push service.
Windows::Devices::Enumeration::DeviceInformationCollection services{
co_await Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(
Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService::GetDeviceSelector(
Windows::Devices::Bluetooth::Rfcomm::RfcommServiceId::ObexObjectPush())) };
if (services.Size() > 0)
{
// Initialize the target Bluetooth BR device.
Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService service{
co_await Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService::FromIdAsync(services.GetAt(0).Id()) };
// Check that the service meets this App's minimum
// requirement
if (SupportsProtection(service)
&& co_await IsCompatibleVersion(service))
{
m_service = service;
// Create a socket and connect to the target
co_await m_socket.ConnectAsync(
m_service.ConnectionHostName(),
m_service.ConnectionServiceName(),
Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication);
// The socket is connected. At this point the App can
// wait for the user to take some action, for example, click
// a button to send a file to the device, which could
// invoke the Picker and then send the picked file.
// The transfer itself would use the Sockets API and
// not the Rfcomm API, and so is omitted here for
//brevity.
}
}
}
// This App requires a connection that is encrypted but does not care about
// whether it's authenticated.
bool SupportsProtection(Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService const& service)
{
switch (service.ProtectionLevel())
{
case Windows::Networking::Sockets::SocketProtectionLevel::PlainSocket:
if ((service.MaxProtectionLevel() == Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionWithAuthentication)
|| (service.MaxProtectionLevel() == Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication))
{
// The connection can be upgraded when opening the socket so the
// App may offer UI here to notify the user that Windows may
// prompt for a PIN exchange.
return true;
}
else
{
// The connection cannot be upgraded so an App may offer UI here
// to explain why a connection won't be made.
return false;
}
case Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionWithAuthentication:
return true;
case Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication:
return true;
}
return false;
}
// This application relies on CRC32 checking available in version 2.0 of the service.
const uint32_t SERVICE_VERSION_ATTRIBUTE_ID{ 0x0300 };
const byte SERVICE_VERSION_ATTRIBUTE_TYPE{ 0x0A }; // UINT32.
const uint32_t MINIMUM_SERVICE_VERSION{ 200 };
Windows::Foundation::IAsyncOperation<bool> IsCompatibleVersion(Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService const& service)
{
auto attributes{
co_await service.GetSdpRawAttributesAsync(Windows::Devices::Bluetooth::BluetoothCacheMode::Uncached) };
auto attribute{ attributes.Lookup(SERVICE_VERSION_ATTRIBUTE_ID) };
auto reader{ Windows::Storage::Streams::DataReader::FromBuffer(attribute) };
// The first byte contains the attribute's type.
byte attributeType{ reader.ReadByte() };
if (attributeType == SERVICE_VERSION_ATTRIBUTE_TYPE)
{
// The remainder is the data
uint32_t version{ reader.ReadUInt32() };
co_return (version >= MINIMUM_SERVICE_VERSION);
}
}
...
Windows::Devices::Bluetooth::Rfcomm::RfcommDeviceService^ _service;
Windows::Networking::Sockets::StreamSocket^ _socket;
void Initialize()
{
// Enumerate devices with the object push service
create_task(
Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(
RfcommDeviceService::GetDeviceSelector(
RfcommServiceId::ObexObjectPush)))
.then([](DeviceInformationCollection^ services)
{
if (services->Size > 0)
{
// Initialize the target Bluetooth BR device
create_task(RfcommDeviceService::FromIdAsync(services[0]->Id))
.then([](RfcommDeviceService^ service)
{
// Check that the service meets this App's minimum
// requirement
if (SupportsProtection(service)
&& IsCompatibleVersion(service))
{
_service = service;
// Create a socket and connect to the target
_socket = ref new StreamSocket();
create_task(_socket->ConnectAsync(
_service->ConnectionHostName,
_service->ConnectionServiceName,
SocketProtectionLevel
::BluetoothEncryptionAllowNullAuthentication)
.then([](void)
{
// The socket is connected. At this point the App can
// wait for the user to take some action, for example, click
// a button to send a file to the device, which could
// invoke the Picker and then send the picked file.
// The transfer itself would use the Sockets API and
// not the Rfcomm API, and so is omitted here for
//brevity.
});
}
});
}
});
}
// This App requires a connection that is encrypted but does not care about
// whether it's authenticated.
bool SupportsProtection(RfcommDeviceService^ service)
{
switch (service->ProtectionLevel)
{
case SocketProtectionLevel->PlainSocket:
if ((service->MaxProtectionLevel == SocketProtectionLevel
::BluetoothEncryptionWithAuthentication)
|| (service->MaxProtectionLevel == SocketProtectionLevel
::BluetoothEncryptionAllowNullAuthentication))
{
// The connection can be upgraded when opening the socket so the
// App may offer UI here to notify the user that Windows may
// prompt for a PIN exchange.
return true;
}
else
{
// The connection cannot be upgraded so an App may offer UI here
// to explain why a connection won't be made.
return false;
}
case SocketProtectionLevel::BluetoothEncryptionWithAuthentication:
return true;
case SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication:
return true;
}
return false;
}
// This App relies on CRC32 checking available in version 2.0 of the service.
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A; // UINT32
const uint MINIMUM_SERVICE_VERSION = 200;
bool IsCompatibleVersion(RfcommDeviceService^ service)
{
auto attributes = await service->GetSdpRawAttributesAsync(
BluetoothCacheMode::Uncached);
auto attribute = attributes[SERVICE_VERSION_ATTRIBUTE_ID];
auto reader = DataReader.FromBuffer(attribute);
// The first byte contains the attribute's type
byte attributeType = reader->ReadByte();
if (attributeType == SERVICE_VERSION_ATTRIBUTE_TYPE)
{
// The remainder is the data
uint version = reader->ReadUInt32();
return version >= MINIMUM_SERVICE_VERSION;
}
}
以服务器的形式接收文件
另一种常见的 RFCOMM 应用方案是在电脑上托管服务,并为其他设备公开该服务。
- 创建 RfcommServiceProvider 以播发所需的服务。
- 根据需要设置 SDP 属性(使用已建立的数据帮助程序生成属性的数据),并开始播发 SDP 记录以供其他设备检索。
- 若要连接到客户端设备,请创建套接字侦听器以开始侦听传入的连接请求。
- 收到连接后,存储连接的套接字以供以后处理。
- 按照已建立的数据流模式从套接字的 InputStream 中读取数据区块,并将其保存至文件。
若要在后台保留 RFCOMM 服务,请使用 RfcommConnectionTrigger。 后台任务在连接到服务时触发。 开发人员在后台任务中接收套接字的句柄。 后台任务将长期运行,其在套接字使用期间一直保留。
Windows.Devices.Bluetooth.Rfcomm.RfcommServiceProvider _provider;
async void Initialize()
{
// Initialize the provider for the hosted RFCOMM service
_provider =
await Windows.Devices.Bluetooth.Rfcomm.RfcommServiceProvider.CreateAsync(
RfcommServiceId.ObexObjectPush);
// Create a listener for this service and start listening
StreamSocketListener listener = new StreamSocketListener();
listener.ConnectionReceived += OnConnectionReceivedAsync;
await listener.BindServiceNameAsync(
_provider.ServiceId.AsString(),
SocketProtectionLevel
.BluetoothEncryptionAllowNullAuthentication);
// Set the SDP attributes and start advertising
InitializeServiceSdpAttributes(_provider);
_provider.StartAdvertising(listener);
}
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A; // UINT32
const uint SERVICE_VERSION = 200;
void InitializeServiceSdpAttributes(RfcommServiceProvider provider)
{
Windows.Storage.Streams.DataWriter writer = new Windows.Storage.Streams.DataWriter();
// First write the attribute type
writer.WriteByte(SERVICE_VERSION_ATTRIBUTE_TYPE);
// Then write the data
writer.WriteUInt32(MINIMUM_SERVICE_VERSION);
IBuffer data = writer.DetachBuffer();
provider.SdpRawAttributes.Add(SERVICE_VERSION_ATTRIBUTE_ID, data);
}
void OnConnectionReceivedAsync(
StreamSocketListener listener,
StreamSocketListenerConnectionReceivedEventArgs args)
{
// Stop advertising/listening so that we're only serving one client
_provider.StopAdvertising();
listener.Dispose();
_socket = args.Socket;
// The client socket is connected. At this point the App can wait for
// the user to take some action, for example, click a button to receive a file
// from the device, which could invoke the Picker and then save the
// received file to the picked location. The transfer itself would use
// the Sockets API and not the Rfcomm API, and so is omitted here for
// brevity.
}
...
#include <winrt/Windows.Devices.Bluetooth.Rfcomm.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.h>
...
Windows::Devices::Bluetooth::Rfcomm::RfcommServiceProvider m_provider{ nullptr };
Windows::Networking::Sockets::StreamSocket m_socket;
Windows::Foundation::IAsyncAction Initialize()
{
// Initialize the provider for the hosted RFCOMM service.
auto provider{ co_await Windows::Devices::Bluetooth::Rfcomm::RfcommServiceProvider::CreateAsync(
Windows::Devices::Bluetooth::Rfcomm::RfcommServiceId::ObexObjectPush()) };
m_provider = provider;
// Create a listener for this service and start listening.
Windows::Networking::Sockets::StreamSocketListener listener;
listener.ConnectionReceived({ this, &MainPage::OnConnectionReceived });
co_await listener.BindServiceNameAsync(m_provider.ServiceId().AsString(),
Windows::Networking::Sockets::SocketProtectionLevel::BluetoothEncryptionAllowNullAuthentication);
// Set the SDP attributes and start advertising
InitializeServiceSdpAttributes();
m_provider.StartAdvertising(listener);
}
const uint32_t SERVICE_VERSION_ATTRIBUTE_ID{ 0x0300 };
const byte SERVICE_VERSION_ATTRIBUTE_TYPE{ 0x0A }; // UINT32.
const uint32_t SERVICE_VERSION{ 200 };
void InitializeServiceSdpAttributes()
{
Windows::Storage::Streams::DataWriter writer;
// First write the attribute type
writer.WriteByte(SERVICE_VERSION_ATTRIBUTE_TYPE);
// Then write the data
writer.WriteUInt32(SERVICE_VERSION);
auto data{ writer.DetachBuffer() };
m_provider.SdpRawAttributes().Insert(SERVICE_VERSION_ATTRIBUTE_ID, data);
}
void OnConnectionReceived(
Windows::Networking::Sockets::StreamSocketListener const& listener,
Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs const& args)
{
// Stop advertising/listening so that we're only serving one client
m_provider.StopAdvertising();
listener.Close();
m_socket = args.Socket();
// The client socket is connected. At this point the application can wait for
// the user to take some action, for example, click a button to receive a
// file from the device, which could invoke the Picker and then save
// the received file to the picked location. The transfer itself
// would use the Sockets API and not the Rfcomm API, and so is
// omitted here for brevity.
}
...
Windows::Devices::Bluetooth::Rfcomm::RfcommServiceProvider^ _provider;
Windows::Networking::Sockets::StreamSocket^ _socket;
void Initialize()
{
// Initialize the provider for the hosted RFCOMM service
create_task(Windows::Devices::Bluetooth.
RfcommServiceProvider::CreateAsync(
RfcommServiceId::ObexObjectPush))
.then([](RfcommServiceProvider^ provider) -> task<void> {
_provider = provider;
// Create a listener for this service and start listening
auto listener = ref new StreamSocketListener();
listener->ConnectionReceived += ref new TypedEventHandler<
StreamSocketListener^,
StreamSocketListenerConnectionReceivedEventArgs^>
(&OnConnectionReceived);
return create_task(listener->BindServiceNameAsync(
_provider->ServiceId->AsString(),
SocketProtectionLevel
::BluetoothEncryptionAllowNullAuthentication));
}).then([listener](void) {
// Set the SDP attributes and start advertising
InitializeServiceSdpAttributes(_provider);
_provider->StartAdvertising(listener);
});
}
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A; // UINT32
const uint SERVICE_VERSION = 200;
void InitializeServiceSdpAttributes(RfcommServiceProvider^ provider)
{
auto writer = ref new Windows::Storage::Streams::DataWriter();
// First write the attribute type
writer->WriteByte(SERVICE_VERSION_ATTRIBUTE_TYPE);
// Then write the data
writer->WriteUInt32(SERVICE_VERSION);
auto data = writer->DetachBuffer();
provider->SdpRawAttributes->Add(SERVICE_VERSION_ATTRIBUTE_ID, data);
}
void OnConnectionReceived(
StreamSocketListener^ listener,
StreamSocketListenerConnectionReceivedEventArgs^ args)
{
// Stop advertising/listening so that we're only serving one client
_provider->StopAdvertising();
create_task(listener->Close())
.then([args](void) {
_socket = args->Socket;
// The client socket is connected. At this point the App can wait for
// the user to take some action, for example, click a button to receive a
// file from the device, which could invoke the Picker and then save
// the received file to the picked location. The transfer itself
// would use the Sockets API and not the Rfcomm API, and so is
// omitted here for brevity.
});
}