読者です 読者をやめる 読者になる 読者になる

kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

SPP通信用Bluetooth Socket Server

概要

リファレンス:Bluetooth Socket

WindowsではBluetoothはSocketに統合されている。特にBluetooth Socketのサーバーを立てる際はBluetooth用のAPIを使う必要が無く、通常のSocketをBluetooth用の構造体や値で初期化してやれば使えるようになる。ただし、WSASetServiceの呼び出しが必要で、これを正しく呼び出していないと、クライアントから接続できない。

  1. listen用ソケット生成
  2. SOCKADDR_BTHでbind
  3. getsocknameでポート番号などを取得
  4. WSASetServiceの呼び出し
  5. listenの呼び出し
  6. accept
  7. recv/sendで通信

準備

必要なヘッダーは2つ。Bluetooth用の構造体などはws2bthに定義されている。

#include <WinSock2.h>
#include <ws2bth.h>

listen用ソケットの生成とbind

リファレンス:Bluetooth and socket, Bluetooth and bind

専用のdefineを用いてSocketを生成する。SOCKADDR_BTHはaddressFamilyを指定する程度で、それ以外は指定する必要が無い。bind時に自動的に割り当てられる。実際に割り当てられた値などは、bind後にgetsocknameで取得する。

SOCKET listen_sock = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (listen_sock == INVALID_SOCKET) {
     return -1;
}

SOCKADDR_BTH sa = { 0 };
sa.addressFamily = AF_BTH;
sa.port = BT_PORT_ANY;
if (bind(listen_sock, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR) {
     return -1;
}
int size = sizeof(sa);
getsockname(listen_sock, (SOCKADDR *)&sa, &size);

WSASetService

リファレンス:Bluetooth and WSASetServiceBluetooth and WSAQUERYSET

呼び出しに複雑な構造体を渡す必要があるが、上記リファレンスに従えばよい。難しいことをしないなら標準値を設定して呼び出せと、標準値はコレだというリストが用意されている。ポイントは2つ。

  • lpServiceClassIdにBluetooth通信のプロファイル対応する値を指定する(SPPはSerialPortServiceClass_UUID)
  • lpcsaBufferのCSADDR_INFO構造体に、サーバーがlistenに使ったSOCKADDR_BTHやプロトコルを指定する
CSADDR_INFO info = { 0 };
info.LocalAddr.lpSockaddr = (LPSOCKADDR)&sa;
info.LocalAddr.iSockaddrLength = sizeof(sa);
info.iSocketType = SOCK_STREAM;
info.iProtocol = BTHPROTO_RFCOMM;

WSAQUERYSET set = { 0 };
set.dwSize = sizeof(WSAQUERYSET);                              // Must be set to sizeof(WSAQUERYSET)
set.dwOutputFlags = 0;                                         // Not used
set.lpszServiceInstanceName = "Server";                       // Recommended.
set.lpServiceClassId = (LPGUID)&SerialPortServiceClass_UUID;   // Requred.
set.lpVersion = NULL;                                          // Not used.
set.lpszComment = NULL;                                        // Optional.
set.dwNameSpace = NS_BTH;                                      // Must be NS_BTH.
set.lpNSProviderId = NULL;                                     // Not required.
set.lpszContext = NULL;                                        // Not used.
set.dwNumberOfProtocols = 0;                                   // Not used.
set.lpafpProtocols = NULL;                                     // Not used.
set.lpszQueryString = NULL;                                    // not used.
set.dwNumberOfCsAddrs = 1;                                     // Must be 1.
set.lpcsaBuffer = &info;                                       // Pointer to a CSADDR_INFO.
set.lpBlob = NULL;                                             // Optional.

WSASetService(&set, RNRSERVICE_REGISTER, 0);

accept

WSASetServiceの呼び出しが成功すれば、通常通りacceptにてクライアントの接続を待機し、接続があれば、生成されるソケットを用いてrecv/sendで通信を行う。SPPであれば通常のソケットと同じく通信ができる。

SOCKADDR_BTH sab2;
int ilen = sizeof(sab2);
socket = accept(listen_sock, (SOCKADDR *)&sab2, &ilen);

全ソース

#include <iostream>
#include <WinSock2.h>
#include <ws2bth.h>

#pragma comment(lib, "Ws2_32")

int main()
{
    WSAData wsaData = { 0 };
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listen_sock = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
    if (listen_sock == INVALID_SOCKET) {
        return -1;
    }

    SOCKADDR_BTH sa = { 0 };
    sa.addressFamily = AF_BTH;
    sa.port = BT_PORT_ANY;
            
    if (bind(listen_sock, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR) {
        return -1;
    }
            
    int size = sizeof(sa);
    getsockname(listen_sock, (SOCKADDR *)&sa, &size);
        
    CSADDR_INFO info = { 0 };
    info.LocalAddr.lpSockaddr = (LPSOCKADDR)&sa;
    info.LocalAddr.iSockaddrLength = sizeof(sa);
    info.iSocketType = SOCK_STREAM;
    info.iProtocol = BTHPROTO_RFCOMM;

    WSAQUERYSET set = { 0 };
    set.dwSize = sizeof(WSAQUERYSET);                              // Must be set to sizeof(WSAQUERYSET)
    set.dwOutputFlags = 0;                                         // Not used
    set.lpszServiceInstanceName = "Server";                        // Recommended.
    set.lpServiceClassId = (LPGUID)&SerialPortServiceClass_UUID;   // Requred.
    set.lpVersion = NULL;                                          // Not used.
    set.lpszComment = NULL;                                        // Optional.
    set.dwNameSpace = NS_BTH;                                      // Must be NS_BTH.
    set.lpNSProviderId = NULL;                                     // Not required.
    set.lpszContext = NULL;                                        // Not used.
    set.dwNumberOfProtocols = 0;                                   // Not used.
    set.lpafpProtocols = NULL;                                     // Not used.
    set.lpszQueryString = NULL;                                    // not used.
    set.dwNumberOfCsAddrs = 1;                                     // Must be 1.
    set.lpcsaBuffer = &info;                                       // Pointer to a CSADDR_INFO.
    set.lpBlob = NULL;                                             // Optional.

    if (WSASetService(&set, RNRSERVICE_REGISTER, 0) != 0) {
        return -1;
    }
    
    listen(listen_sock, 0);

    SOCKADDR_BTH sab2;
    int ilen = sizeof(sab2);
    SOCKET socket = accept(listen_sock, (SOCKADDR *)&sab2, &ilen);

    char buf[1024] = { 0 };
    int res = recv(socket, buf, sizeof(buf), 0);
    
    if (res > 0) {
        std::cout << buf << std::endl;
    }

    closesocket(listen_sock);
    closesocket(socket);

    WSACleanup();
}