Опубликован: 11.01.2013 | Доступ: свободный | Студентов: 623 / 124 | Длительность: 12:06:00
Лекция 9:

Лабораторный практикум по технологиям Bluetooth и Wi-Fi

< Лекция 8 || Лекция 9: 12345

Создание и поиск сервисов

Лабораторная работа №2 посвящена Bluetooth-сервисам. Сервис – это программа-поставщик какого-либо вида услуг, предусматривающая определенный протокол общения с клиентом. Сервис идентифицируется с помощью уникального идентификатора UUID. Примером стандартных сервисов могут служить File Transfer, FAX, LAN Access и др. Клиент подключается именно к сервису, а уже на сервисе может быть запущено несколько серверов, ожидающих соединения. Если программа собирается предоставлять новый тип услуг, она должна зарегистрировать новый сервис.

Перед тем как подключаться к устройству, нужно сначала проверить, запущен ли на нем нужный сервис. Кроме того, перед подключением обычно необходимо узнать на каком порту данный сервис ожидает клиента. Для этого служит процедура обнаружения сервисов.

Постановка задачи

Создать программу, которая регистрирует новый сервис с уникальным именем и UUID.

Изменить программу, полученную в результате выполнения лабораторной работы №1 таким образом, чтобы она находила все сервисы на удаленном устройстве. Для каждого сервиса необходимо вывести на экран его имя и порт, который должен использоваться для подключения к этому сервису. Рекомендуется использовать следующий порядок вывода на экран:

Device 1 Name
    Service 1.1 Name (port = 1)
    Service 1.2 Name (port = 2)
Device 2 Name
    Service 2.1 Name (port = 1)
    Service 2.2 Name (port = 3)
    Service 2.3 Name (port = 5)
Device 3 Name
    Service 3.1 Name (port = 7)

При сдаче работы на одном компьютере необходимо будет запустить программу, создающую сервис, на другом – программу, осуществляющую поиск всех устройств и сервисов. Правильное выполнение будет засчитано, если новый сервис будет найден на правильном устройстве.

Методические рекомендации

Создание сервиса

Создание и удаление сервиса осуществляется с помощью функции WSASetService().

int WSASetService(LPWSAQUERYSET    lpqsRegInfo,
                  WSAESETSERVICEOP essOperation,
                  DWORD            dwControlFlags);

При создании сервиса essOperation должен равняться RNRSERVICE_REGISTER, при удалении - RNRSERVICE_DELETE.dwControlFlags всегда должен быть равен 0.

Параметры нового сервиса настраиваются через уже знакомую структуру WSAQUERYSET. На этот раз ее размера хватит, и не надо создавать буфер большего размера.

При регистрации сервиса, во-первых, как обычно, нужно задать поля dwSize и dwNameSpace, во-вторых, полю lpszServiceInstanceName нужно присвоить указатель на имя нового сервиса, а в поле lpServiceClassId поместить указатель на его UUID.

Подсказка: для создания нового UUID в Visual Studio выполните Tools>Create GUID>Пункт 3, нажмите Copy и затем вставьте строку в нужном месте программы.

И, наконец, нужно заполнить информацию о сетевом адресе сервиса. Для этого служат поле lpcsaBuffer, содержащее массив сетевых адресов, и поле dwNumberOfCsAddrs – количество адресов в буфере. Bluetooth-сервис имеет только один сетевой адрес, поэтому dwNumberOfCsAddrs должен быть равен 1.

Поле lpcsaBuffer является указателем на структуру CSADDR_INFO, в которой содержится локальный и удаленный Bluetooth-адреса, а также тип соединения и тип используемого протокола.

struct CSADDR_INFO
{
    SOCKET_ADDRESS LocalAddr;
    SOCKET_ADDRESS RemoteAddr;
    int iSocketType;
    int iProtocol;
};

Для Bluetooth поле iSocketType должно равняться SOCK_STREAM, а поле iProtocol - BTHPROTO_RFCOMM.

Поскольеу мы создаем сервис на локальном устройстве, нам нужно заполнить поле LocalAddr. Оно имеет тип SOCKET_ADDRESS.

struct SOCKET_ADDRESS
{
    LPSOCKADDR lpSockaddr;
    int        iSockaddrLength;
};

Данная структура содержит информацию о сетевом адресе – указатель на сокетный адрес и размер этого адреса, т.к. его формат зависит от типа связи. Для Bluetooth сокетный адрес выглядит следующим образом:

struct SOCKADDR_BTH
{
    USHORT   addressFamily;
    BTH_ADDR btAddr;
    GUID     serviceClassId;
    ULONG    port;
}; 

addressFamily должен всегда быть равен AF_BTH. В поле port необходимо указать номер порта для сервиса. Если какому-то сервису уже назначен такой порт, то WSASetService() завершится с ошибкой. Номер порта выделяется при создании сервером сокета, который нужен для создания соединения. В данной лабораторной работе мы не будем создавать соединение, нам нужно только создать сервис. Поэтому полю port можно присвоить значение BT_PORT_ANY, что означает, что сервис может находиться на любом порте. Остальные поля для регистрации сервиса у этих структур заполнять не надо.

Замечание: если вы заполняете не все поля структур, не забывайте вначале инициализировать их нулями! В противном случае из-за этого могут возникнуть непонятные ошибки. Например, это можно сделать так:

SOCKADDR_BTH address;
ZeroMemory(&address, sizeof(address));
// Заполнение нужных полей

CSADDR_INFO addrInfo;
ZeroMemory(&addrInfo, sizeof(addrInfo));
addrInfo.LocalAddr.iSockaddrLength = sizeof(SOCKADDR_BTH);
addrInfo.LocalAddr.lpSockaddr = (sockaddr*)&address;
// Заполнение остальных полей

WSAQUERYSET querySet;
ZeroMemory(&querySet, sizeof(querySet));
querySet.lpcsaBuffer = &addrInfo;
// Заполнение остальных полей

После настройки всех параметров можно вызывать WSASetService(). При правильной регистрации сервиса она должна вернуть 0.

Замечание: после закрытия программы, даже если сервис не был удален, он может исчезнуть, поэтому лучше не закрывайте программу до тех пор, пока вы не обнаружите свой сервис

Поиск сервисов

Для получения списка сервисов, доступных на устройстве, необходимо лишь знать адрес этого устройства. Его можно получить в результате их поиска. Информацию о сервисах можно получать как при нахождении нового устройства, так и после нахождения всех устройств, но для этого нужно хранить их список.

Поиск сервисов производится аналогично поиску устройств, описанному в указаниях к прошлой лабораторной работе, за исключением нескольких отличий:

  1. Перед инициализацией поиска поле lpszContext структуры WSAQUERYSET должно содержать адрес удаленного устройства в виде строки. Его можно получить с помощью функции WSAAddressToString(), передав в нее адрес нужного устройства (он уже должен быть известен).
    DWORD addressLength = 128;
    char* addressString = new char[addressLength];
    
    int result = WSAAddressToString((SOCKADDR*)address,
                  sizeof(SOCKADDR_BTH),
                  NULL,
                  addressString,
                  &addressLength);
    if (result != 0)
    {
        // Ошибка
    }
    
    pQuerySet->lpszContext = addressString;
    

    address – это сокетный адрес удаленного устройства, он находится в поле lpcsaBuffer->RemoteAddr.lpSockaddr структуры WSAQUERYSET, полученной при поиске устройства (см. лабораторную работу №2)

  2. Поле lpServiceClassId должно содержать UUID сервиса. Для поиска всех сервисов нужно установить его значение в L2CAP_PROTOCOL_UUID.
  3. Для инициализации поиска сервисов параметр dwControlFlags функции WSALookupServiceBegin() не должен содержать LUP_CONTAINERS, он должен быть либо равен LUP_NONE, чтобы брать информацию из кэша, либо равен LUP_FLUSHCACHE, чтобы запустить полный поиск.
  4. Для получения имени сервиса dwControlFlags функции WSALookupServiceNext() должен также содержать LUP_RETURN_NAME (имя опять будет содержаться в поле lpszServiceInstanceName). Для получения номера порта, который должен использоваться для подключения к сервису, нужно скомбинировать его с LUP_RETURN_ADDR, тогда он будет находиться в
    ((SOCKADDR_BTH*)pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr)->port
    

Замечание: Не забывайте вызывать в начале работы WSAStartup(), а в конце WSACleanup().

< Лекция 8 || Лекция 9: 12345