一、需求
在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(从机),允许外来设备(主机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为Linux C
二、环境准备
bluez安装
linux C在终端中输入以下命令,安装BlueZ库:
sudo apt-get update
sudo apt-get install bluez
sudo apt-get install libbluetooth-dev
修改配置文件
修改 /etc/systemd/system/dbus-org.bluez.service
在ExecStart =/usr/lib/Bluetooth/bluetoothd 后面添加-C
紧接着添加一行:ExecStartPost=/usr/bin/sdptool add SP
其中修改系统中蓝牙服务的启动选项,-C的意思就是compat,兼容性模式运行蓝牙服务;sdptool add SP是为了开机自启动SPP服务,默认是把这个服务放到channel =1的通道中,这个通道类似于socket的端口号。
再reboot重启跟新配置
检查蓝牙设备是否加载成功
hciconfig检查蓝牙加载情况,正常启动显示如下:
root@orangepizero2:/home/orangepi# hciconfig
hci0: Type: Primary Bus: UART
BD Address: 63:E8:09:BF:10:A5 ACL MTU: 1021:8 SCO MTU: 240:3
UP RUNNING
RX bytes:744 acl:0 sco:0 events:51 errors:0
TX bytes:5366 acl:0 sco:0 commands:51 errors:0
蓝牙命令行操作(非必须)
如果想改变蓝牙的配置或查询状态等,可以通过bluetoothctl的命令行进行操作,具体可以参考这篇博文:
https://blog.csdn.net/lxyoucan/article/details/124705648
三、服务端程序
代码思路
在主函数中创建一个用于广播信息的线程sendmsg_func;广播时,往在线的客户端发送相同消息
然后主函数处于监听状态,等待外来蓝牙客户端的接入,为每一个接入的客户端生成对应的recv_func线程,同时允许最多20个蓝牙客户端接入(其实蓝牙即使开了主从模式也接受不了这么多从机接入,容易出现不稳定的情况,所以这里设定20个客户端已经很大);
其中,客户端套接字数组c_fd[ClientMax]
都会初始化为-1,当客户端套接字被使用后离线,程序会将该套接字的值重新置为-1,表明该套接字未被占用,后续接入的客户端可以使用该套接字;
具体实现BluetoothServer2.c如下,代码已经详细注释:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <pthread.h>
#define ClientMax 20
#define BUFSIZE 512
int c_fd[ClientMax];
char recBuf[BUFSIZE] = {0}; //用于记录接入的客户端的mac地址
/*******************
用于广播信息到各个蓝牙的线程,广播的消息这里通过终端直接输入
的形式,实际应用时,可自行修改为其他信息源
*******************/
void *sendmsg_func(void *p)
{
int j;
printf("启动信息发送线程:\n");
printf("直接在空白处输入即可\n");
char sendBuf[BUFSIZE] = {'\0'}; //用于存储要广播的消息
while(1)
{
memset(sendBuf,0,BUFSIZE);
fgets(sendBuf,BUFSIZE,stdin); //用于用户输入要广播的消息
//给所有在线的客户端发送信息
for(j = 0;c_fd[j] > 0 && j < ClientMax;j++)
{
if (c_fd[j] == -1)
{
continue; //如果是已退出或未使用的客户端,则不发送信息
}
else
{
if(write(c_fd[j],sendBuf,BUFSIZE) < 0 )
{
perror("write");
exit(-1);
}
}
}
}
}
/*******************
用于接收新接入的蓝牙客户端消息
*******************/
void *recv_func(void *p)
{
int tmp_c_fd = *((int *)p); //拿到接入的客户端的套接字
char nameBuf[BUFSIZE] = {0}; //存储接入的客户端的mac地址,用于区别不同客户端
char readBuf[BUFSIZE] = {0}; //用于存储接收到对应客户端的消息
int n_read = 0;
//将全局变量recBuf接收到的mac地址,copy到nameBuf中
strcpy(nameBuf,recBuf); //这里其实最好要考虑线程并发对recBuf值的改变,可以考虑使用互斥量等方法
pthread_t tid;
tid = pthread_self();
printf("启动线程tid:%lu,用于接收新蓝牙从机%s的信息\n" ,tid,nameBuf);
while(1)
{
memset(readBuf,0,BUFSIZE);
n_read = read(tmp_c_fd,readBuf,sizeof(readBuf));
if(n_read <= 0)
{
//perror("read"); //调试语句
printf("%s中断或者下线了\n",nameBuf);
tmp_c_fd = -1; //如果对应的客户端退出,则令对应的c_fd的值为-1,表示掉线
pthread_exit(NULL); //如果客户端掉线,结束线程
}
else
{
printf("%s:#%s\n",nameBuf,readBuf); //将用户发送的信息打印在服务端,若有数据库,这里可以将聊天记录存在数据库
}
}
}
int main()
{
struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
int s,bytes_read,i,err,ret;
pthread_t rec_tid[ClientMax] = {0};
pthread_t send_tid;
int opt = sizeof(rem_addr);
//让本机蓝牙处于可见状态
ret = system("hciconfig hci0 piscan");
if(ret < 0)
{
perror("bluetooth discovering fail");
}
// 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; //相当于tcp的ip地址
loc_addr.rc_channel = (uint8_t) 1; //这里的通道就是SPP的通道,相当于网络编程里的端口
bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
// put socket into listening mode
listen(s, ClientMax);
printf("bluetooth_server listen success\n");
//初始化客户端套接字
for(i = 0;i < ClientMax;i++)
{
c_fd[i] = -1;
}
//创建线程用于广播消息
err = pthread_create(&send_tid,NULL,sendmsg_func,NULL);
if(err)
{
fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
exit(1);
}
//不断等待是否有新蓝牙接入
while(1)
{
i = 0;
//从数组中选取一个可用的客户端套接字,值等于-1即为可用的套接字
while(1)
{
if((i < ClientMax) && (c_fd[i] != -1))
{
i++;
}
else if(i >= ClientMax)
{
fprintf(stderr,"client fd has more than 20\n");
exit(-1);
}
else
{
break;
}
}
//accept新的蓝牙接入
c_fd[i] = accept(s, (struct sockaddr *)&rem_addr, &opt);
if (c_fd[i] > 0){
printf("client connected success\n");
}
else{
printf("accept client fail\n");
continue;
}
// ba2str把6字节的bdaddr_t结构
//转为为形如XX:XX:XX:XX:XX:XX(XX标识48位蓝牙地址的16进制的一个字节)的字符串
ba2str( &rem_addr.rc_bdaddr, recBuf);
fprintf(stdout, "accepted connection from %s\n", recBuf);
//为每个新的客户端创建自己的线程用于接收信息
err = pthread_create((rec_tid+i),NULL,recv_func,(c_fd+i));
if (err)
{
fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
exit(1);
}
}
// close connection
//close(client);
close(s);
return 0;
}
编译语句
将BluetoothServer2.c编译为可执行文件BluetoothServer2
gcc -o BluetoothServer2 BluetoothServer2.c -lbluetooth -lpthread
执行结果
开启服务端后,分别用两台手机的蓝牙接入服务端,并向服务端发送消息;然后服务端再广播消息到两台设备上
服务端结果
手机蓝牙1
手机蓝牙2
可以看到已经可以实现多客户端蓝牙通信;
局限性
未考虑多并发的情况,所以代码可以引入互斥量、条件变量等极致,防止因为并发导致的数据不准确