建立和使用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。一旦建立了連接,讀、寫、發送和接收的標准調用就可以用於數據傳輸。