蓝牙编程扫盲 L2CAP sockets

与RFCOMM一样,L2CAP通信是围绕套接字编程构建的。例4-4和例4-5演示了如何建立L2CAP信道并传输短串数据。为了简单起见,客户端被硬编码为连接到“01:23:45:67:89:AB”。

l2cap-server.c 代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>

int main(int argc, char **argv)
{
    struct sockaddr_l2 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_SEQPACKET, BTPROTO_L2CAP);

    // bind socket to port 0x1001 of the first available 
    // bluetooth adapter
    loc_addr.l2_family = AF_BLUETOOTH;
    loc_addr.l2_bdaddr = *BDADDR_ANY;
    loc_addr.l2_psm = htobs(0x1001);

    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.l2_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);
}

l2cap-client.c 代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>

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

    if(argc < 2)
    {
        fprintf(stderr, "usage: %s <bt_addr>\n", argv[0]);
        exit(2);
    }

    strncpy(dest, argv[1], 18);

    // allocate a socket
    s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);

    // set the connection parameters (who to connect to)
    addr.l2_family = AF_BLUETOOTH;
    addr.l2_psm = htobs(0x1001);
    str2ba( dest, &addr.l2_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);
}

对于简单的使用场景,唯一的区别是指定的套接字类型、协议族和寻址结构。默认情况下,L2CAP连接提供可靠的面向数据报的连接,数据包按顺序传递,因此套接字类型为SOCK_SEQPACKET,协议为BTPROTO_L2CAP。寻址结构struct sockaddr_l2与RFCOMM寻址结构略有不同

struct sockaddr_l2 {
    sa_family_t     l2_family;
    unsigned short  l2_psm;
    bdaddr_t        l2_bdaddr;
};

l2_psm字段指定要使用的L2CAP端口号。因为它是一个多字节无符号整数,所以字节排序非常重要。前面描述的htobs函数用于将数字转换为蓝牙字节顺序。

Maximum Transmission Unit

有时,应用程序可能需要调整L2CAP连接的最大传输单元(MTU),并将其设置为默认值672字节以外的值。在BlueZ中,这是通过getsockopt和setsockopt函数完成的。

struct l2cap_options {
    uint16_t    omtu;
    uint16_t    imtu;
    uint16_t    flush_to;
    uint8_t     mode;
};

int set_l2cap_mtu( int sock, uint16_t mtu ) {
	struct l2cap_options opts;
    int optlen = sizeof(opts), err;
    err = getsockopt( s, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen );
    if( ! err ) {
        opts.omtu = opts.imtu = mtu;
        err = setsockopt( s, SOL_L2CAP, L2CAP_OPTIONS, &opts, optlen );
    }
    return err;
};

struct l2cap_options的omtu和imtu字段分别用于指定传出MTU和传入MTU。其他两个字段当前未使用,保留供将来使用。要调整连接范围内的MTU,两个客户端都必须调整其传出和传入MTU。蓝牙允许MTU的范围从最小48字节到最大65535字节。

Unreliable sockets

说L2CAP套接字在默认情况下是可靠的,这有点误导性。两个设备之间的多个L2CAP和RFCOMM连接实际上是在它们之间建立的单个较低级别连接上多路复用的逻辑连接。调整传递语义的唯一方法是针对较低级别的连接调整它们,这反过来会影响两个设备之间的所有L2CAP和RFCOMM连接。

随着我们对蓝牙编程更复杂方面的深入研究,接口变得有点难以管理。不幸的是,BlueZ没有提供一种简单的方法来更改连接的包超时。首先需要底层连接的句柄来进行此更改,但是获得底层连接句柄的唯一方法是在本地蓝牙适配器上查询微控制器。一旦确定了连接手柄,就可以向微控制器发出命令,指示它进行适当的调整。下面的例子展示了如何做到这一点。

#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

int set_flush_timeout(bdaddr_t *ba, int timeout)
{
    int err = 0, dd;
    struct hci_conn_info_req *cr = 0;
    struct hci_request rq = { 0 };

    struct {
        uint16_t handle;
        uint16_t flush_timeout;
    } cmd_param;

    struct {
        uint8_t  status;
        uint16_t handle;
    } cmd_response;

    // find the connection handle to the specified bluetooth device
    cr = (struct hci_conn_info_req*) malloc(
            sizeof(struct hci_conn_info_req) + 
            sizeof(struct hci_conn_info));
    bacpy( &cr->bdaddr, ba );
    cr->type = ACL_LINK;
    dd = hci_open_dev( hci_get_route( &cr->bdaddr ) );
    if( dd < 0 ) {
        err = dd;
        goto cleanup;
    }
    err = ioctl(dd, HCIGETCONNINFO, (unsigned long) cr );
    if( err ) goto cleanup;

    // build a command packet to send to the bluetooth microcontroller
    cmd_param.handle = cr->conn_info->handle;
    cmd_param.flush_timeout = htobs(timeout);
    rq.ogf = OGF_HOST_CTL;
    rq.ocf = 0x28;
    rq.cparam = &cmd_param;
    rq.clen = sizeof(cmd_param);
    rq.rparam = &cmd_response;
    rq.rlen = sizeof(cmd_response);
    rq.event = EVT_CMD_COMPLETE;

    // send the command and wait for the response
    err = hci_send_req( dd, &rq, 0 );
    if( err ) goto cleanup;

    if( cmd_response.status ) {
        err = -1;
        errno = bt_error(cmd_response.status);
    }

cleanup:
    free(cr);
    if( dd >= 0) close(dd);
    return err;
}

成功时,与指定设备的低级别连接的数据包超时设置为超时*0.625毫秒。超时值0用于指示无穷大,并指示如何恢复到可靠的连接。该函数的大部分由代码组成,用于构造与蓝牙控制器通信时使用的命令包和响应包。蓝牙规范定义了这些数据包的结构和幻数0x28。在大多数情况下,BlueZ提供了方便的函数来构造包、发送包并等待响应。然而,设置数据包超时似乎很少使用,因此目前还没有方便的功能。

本人微信:xiaoshitou5854

posted @ 2020-07-25 17:52  小石王  阅读(19)  评论(0编辑  收藏