Bluetooth RFCOMM
В этом разделе представлен обзор Bluetooth RFCOMM в приложениях универсальная платформа Windows (UWP), а также пример кода отправки или получения файла.
Важные API
Внимание
Необходимо объявить функцию Bluetooth в Package.appxmanifest.
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
Обзор
API в пространстве имен Windows.Devices.Bluetooth.Rfcomm создаются на основе существующих шаблонов для Windows.Devices, включая перечисление и создание экземпляров. Чтение и запись данных предназначены для использования установленных шаблонов потока данных и объектов в Windows.Storage.Streams. Атрибуты протокола ОБНАРУЖЕНИЯ служб (SDP) имеют значение и ожидаемый тип. Однако некоторые распространенные устройства имеют неисправные реализации атрибутов SDP, где значение не относится к ожидаемому типу. Кроме того, многие виды использования RFCOMM не требуют дополнительных атрибутов SDP. По этим причинам этот API предлагает доступ к неисправным данным SDP, из которых разработчики могут получить необходимые сведения.
API RFCOMM используют концепцию идентификаторов служб. Хотя идентификатор службы является просто 128-разрядным GUID, он также обычно указывается как 16-разрядное или 32-разрядное целое число. API RFCOMM предлагает оболочку для идентификаторов служб, которая позволяет указывать и использовать их в виде 128-разрядных идентификаторов GUID, а также 32-разрядных целых чисел, но не предлагает 16-разрядных целых чисел. Это не проблема для API, так как языки будут автоматически обновляться до 32-разрядного целого числа, и идентификатор по-прежнему может быть правильно создан.
Приложения могут выполнять многофакторные операции устройства в фоновой задаче, чтобы они могли выполняться до завершения, даже если приложение перемещено в фон и приостановлено. Это позволяет надежно обслуживать устройства, такие как изменения постоянных параметров или встроенного ПО, а также синхронизацию содержимого, не требуя, чтобы пользователь сидел и смотрел индикатор выполнения. Используйте DeviceServicingTrigger для обслуживания устройств и DeviceUseTrigger для синхронизации содержимого. Обратите внимание, что эти фоновые задачи ограничивают время запуска приложения в фоновом режиме и не предназначены для разрешения неограниченной операции или бесконечной синхронизации.
Полный пример кода, который содержит сведения об операции RFCOMM, см. в примере чата Bluetooth Rfcomm на Github.
Отправка файла в качестве клиента
При отправке файла наиболее простым сценарием является подключение к парному устройству на основе требуемой службы. Для этого необходимо выполнить следующие шаги.
- Используйте функции 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 для получения других устройств.
- Чтобы подключиться к клиентскому устройству, создайте прослушиватель сокетов, чтобы начать прослушивание входящих запросов на подключение.
- После получения подключения сохраните подключенный сокет для последующей обработки.
- Следуйте установленным шаблонам потока данных, чтобы считывать фрагменты данных из входного потока сокета и сохранять его в файл.
Чтобы сохранить службу 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.
});
}