與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提供了方便的函數來構造包、發送包並等待響應。然而,設置數據包超時似乎很少使用,因此目前還沒有方便的功能。