建立和使用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
", 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]
", 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。一旦建立了连接,读、写、发送和接收的标准调用就可以用于数据传输。