蓝牙编程扫盲 RFCOMM sockets

建立和使用RFCOMM连接可以归结为我们已经知道如何用于TCP/IP编程的套接字编程技术。唯一的区别是套接字寻址结构不同,我们对多字节整数的字节排序使用了不同的函数。例4-2和例4-3展示了如何使用RFCOMM套接字建立连接,传输一些数据,并断开连接。为了简单起见,客户端被硬编码为连接到“01:23:45:67:89:AB”。

注意:不能在一个机器上运行下面的代码,普通的网络通信可以在一台机器上运行server和client,但蓝牙不行。

rfcomm-server.c 代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>

int main(int argc, char **argv)
{
    struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
    char buf[1024] = { 0 };
    int s, client, bytes_read;
    socklen_t opt = sizeof(rem_addr);

    // allocate socket
    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    // bind socket to port 1 of the first available 
    // local bluetooth adapter
    loc_addr.rc_family = AF_BLUETOOTH;
    loc_addr.rc_bdaddr = *BDADDR_ANY;
    loc_addr.rc_channel = (uint8_t) 1;
    bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));

    // put socket into listening mode
    listen(s, 1);

    // accept one connection
    client = accept(s, (struct sockaddr *)&rem_addr, &opt);

    ba2str( &rem_addr.rc_bdaddr, buf );
    fprintf(stderr, "accepted connection from %s\n", buf);
    memset(buf, 0, sizeof(buf));

    // read data from the client
    bytes_read = read(client, buf, sizeof(buf));
    if( bytes_read > 0 ) {
        printf("received [%s]\n", buf);
    }

    // close connection
    close(client);
    close(s);
    return 0;
}

rfcomm-client.c 代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>

int main(int argc, char **argv)
{
    struct sockaddr_rc addr = { 0 };
    int s, status;
    char dest[18] = "01:23:45:67:89:AB";

    // allocate a socket
    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    // set the connection parameters (who to connect to)
    addr.rc_family = AF_BLUETOOTH;
    addr.rc_channel = (uint8_t) 1;
    str2ba( dest, &addr.rc_bdaddr );

    // connect to server
    status = connect(s, (struct sockaddr *)&addr, sizeof(addr));

    // send a message
    if( status == 0 ) {
        status = write(s, "hello!", 6);
    }

    if( status < 0 ) perror("uh oh");

    close(s);
    return 0;
}

上面的代码对于有经验的网络程序员来说,这其中的大部分应该看起来很熟悉。与因特网编程一样,首先用socket系统调用分配一个套接字。用AF_BLUETOOTH 代替AF-INET,用BTPROTO-RFCOMM代替IPPROTO_TCP 。由于RFCOMM提供与TCP相同的传递语义,SOCK_STREAM仍然可以用于套接字类型。

Addressing structures

要建立与另一个蓝牙设备(传入或传出)的RFCOMM连接,请创建并填充struct sockaddr_rc寻址结构。与TCP/IP中使用的结构struct sockaddr_in 一样,寻址结构指定传出连接或侦听套接字的详细信息。

struct sockaddr_rc {
	sa_family_t	rc_family;
	bdaddr_t	rc_bdaddr;//相当于tcp的ip地址
	uint8_t		rc_channel;//相当于端口号
};

rc_family字段指定套接字的寻址系列,并且始终是AF_BLUETOOTH。对于传出连接,rc_bdaddr和rc_channel分别指定要连接的蓝牙地址和端口号。对于侦听套接字,rc_bdaddr指定要使用的本地蓝牙适配器,并且通常设置为bdaddr_ANY以指示任何本地蓝牙适配器都可以接受。对于侦听套接字,rc_channel指定要侦听的端口号。

A note on byte ordering

由于蓝牙处理的是从一台机器到另一台机器的数据传输,因此对多字节数据类型使用一致的字节顺序是至关重要的。与使用big-endian格式的网络字节排序不同,Bluetooth字节排序是little-endian,即先传输最低有效字节。BlueZ提供了四个方便的函数来在主机和蓝牙字节顺序之间进行转换。

unsigned short int htobs( unsigned short int num );//主机转成蓝牙;s代表short类型
unsigned short int btohs( unsigned short int num );//蓝牙转成主机;s代表short类型
unsigned int htobl( unsigned int num );//主机转成蓝牙;l代表int类型
unsigned int btohl( unsigned int num );//蓝牙转成主机;l代表int类型

与网络顺序对应的函数一样,这些函数将16位和32位无符号整数转换为蓝牙字节顺序并返回。它们用于填充套接字寻址结构、与蓝牙微控制器通信以及在传输协议套接字上执行低级操作时使用。

Dynamically assigned port numbers

对于Linux内核版本2.6.7及更高版本,动态绑定到RFCOMM或L2CAP端口非常简单。用于绑定套接字的套接字寻址结构的rc_channel字段被简单地设置为0,内核将套接字绑定到第一个可用端口。不幸的是,对于早期版本的Linux内核,绑定到第一个可用端口号的唯一方法是尝试绑定到每个可能的端口,并在绑定没有失败时停止。下面的函数演示如何对RFCOMM套接字执行此操作。

int dynamic_bind_rc(int sock, struct sockaddr_rc *sockaddr, uint8_t *port)
{
    int err;
    for( *port = 1; *port <= 31; *port++ ) {
        sockaddr->rc_channel = *port;
        err = bind(sock, (struct sockaddr *)sockaddr, sizeof(sockaddr));
        if( ! err || errno == EINVAL ) break;
    }
    if( port == 31 ) {
        err = -1;
        errno = EINVAL;
    }
    return err;
}

RFCOMM summary

通常使用setsockopt设置的高级TCP选项(如接收窗口和Nagle算法)在蓝牙中没有意义,也不能与RFCOMM套接字一起使用。除此之外,字节顺序和套接字寻址结构的不同,编程RFCOMM套接字实际上与编程TCP套接字完全相同。要使用套接字接受传入连接,请使用bind保留操作系统资源,使用listen将其置于侦听模式,并使用accept阻止和接受传入连接。创建传出连接也很简单,只需要调用connect。一旦建立了连接,读、写、发送和接收的标准调用就可以用于数据传输。

本人微信:xiaoshitou5854

posted @ 2020-07-25 16:38  小石王  阅读(2889)  评论(0编辑  收藏  举报