【Linux C】基于树莓派/香橙派的蓝牙服务端——支持多蓝牙设备接入
一、需求
在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(主机),允许外来设备(从机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为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: UARTBD Address: 63:E8:09:BF:10:A5 ACL MTU: 1021:8 SCO MTU: 240:3UP RUNNINGRX bytes:744 acl:0 sco:0 events:51 errors:0TX 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 sockets = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);// bind socket to port 1 of the first available// local bluetooth adapterloc_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 modelisten(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
可以看到已经可以实现多客户端蓝牙通信;
局限性
未考虑多并发的情况,所以代码可以引入互斥量、条件变量等极致,防止因为并发导致的数据不准确
相关文章:

【Linux C】基于树莓派/香橙派的蓝牙服务端——支持多蓝牙设备接入
一、需求 在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(主机),允许外来设备(从机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为Linux C 二、环境准备 bluez安装 …...

鸿蒙App开发选择Java还是JavaScript?
众所周知, Java和 JavaScript是两种编程语言,这两种语言在不同的环境中都有许多用途。在鸿蒙 App开发中, Java和 JavaScript是两种常见的编程语言,它们都具有广泛的应用,并且都有其独特的优势。下面我们将就这两种编程…...
【Android】CountDownTimer的使用
android中怎么实现倒计时 在Android中,可以使用CountDownTimer类来实现倒计时。以下是一个简单的示例: javaCopy new CountDownTimer(30000, 1000) {public void onTick(long millisUntilFinished) {// 每次倒计时间隔1秒,更新UI上的倒计时剩…...
Linux :: 【基础指令篇 :: 文件及目录操作:(1)】:: ls :: 查看指定目录下的内容
前言:本篇是 Linux 基本操作篇章的内容! 笔者使用的环境是基于腾讯云服务器:CentOS 7.6 64bit。 学习集: C 入门到入土!!!学习合集Linux 从命令到网络再到内核!学习合集 目录索引&am…...

【商品详情 +关键词搜索】API 接口系列
首先,大家要到官方主页去申请一个 appkey,这个是做什么用的呢?App Key 是应用的唯一标识,TOP 通过 App Key 来鉴别应用的身份。AppSecret 是 TOP 给应用分配的密钥,开发者需要妥善保存这个密钥,这个密钥用来…...

RabbitMQ学习-发布确认高级
发布确认springboot版本 确认机制方案: 代码架构图: 配置文件: 在application.properties全局配置文件中添加spring.rabbitmq.publish-confirm-type属性,这个属性有以下几种值 none:禁用发布确认模式(默认)0 correlated:发布消…...
重载和内联函数
函数的默认参数 默认参数是指调用函数的时候,如果不写实参,那么将使用一个缺省值。 使用默认参数可以使你的函数更加灵活,同时减少了在不同上下文中为相同的参数重复编写相同的代码的需要。 return_type function_name(data_type paramete…...
从零学算法
198.你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额…...

《Linux0.11源码解读》理解(四) head之重新设置IDT/GDT
上节提到,现在cs:ip指向0地址,此处存储着作为操作系统核心代码的system模块,是由head.s和 main.c以及后面所有源代码文件编译链接而成。head.s(以下简称head)紧挨着main.c,我们先执行head。 重新设置内核栈 _pg_dir: _startup_3…...
<SQL>《SQL命令(含例句)精心整理版(4)》
《SQL命令(含例句)精心整理版(4)》 14 数据库对象14.1 表14.2 视图14.3 存储过程14.3.1 概念14.3.2 创建存储过程14.3.2 调用存储过程14.3.3 DbVisualizer工具中调用方法14.3.3 DB2命令行脚本调用方法14.3.4 DB2中两个存储过程报错…...
C++死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的状态称为死锁。 死锁通常发生…...

[自学记录02|百人计划]纹理压缩
一、什么是纹理压缩 纹理压缩是为了解决内存、带宽问题,专为在计算机图形渲染系统中存储纹理而使用的图像压缩技术。 1.图片格式和纹理格式的区别 (1)图片格式 图片格式是图片文件的存储格式,通常在磁盘、内存中储存和传输文件时使用;例如…...

C++泛型编程之模板
目录 一、什么是泛型编程 二、函数模板 2.1函数模板的概念 2.2函数模板格式 2.3函数模板的原理 2.5函数模板的实例化 2.6模板参数的匹配原则 三、类模板 3.1类模板的定义格式 3.2 类模板的实例化 四、非类型模板参数 五、模板的特化 5.1模板特化的概念:…...

极氪汽车 APP 系统云原生架构转型实践
作者:极氪汽车 前言 新能源汽车已经成为我国汽车市场再次崛起的关键支柱,随着新能源汽车市场的快速发展,不同类型的品牌造车厂商呈现出百花齐放的态势。极氪汽车是吉利控股集团旗下高端纯电汽车新品牌,2021 年 4 月极氪发布首款…...
一个UDP下载服务器的实现(模拟下载文件)
本期分享的主要是使用UDP实现文件下载功能,需要自己编写服务器和客户端,实现的功能主要有以下几个: (1)服务器可以为请求的用户下发文件数据(前提是服务器得有这个数据文件) (2&…...
01.hadoop上课笔记之hadoop介绍
1.大数据介绍 可以对未来数据预测 google通过搜索预测流感,足球球员有一 定关联…caict可以得到数据hbase hive林子雨mooc数据要进行挖掘(推断更多信息) 2.大数据是非结构化数据多:声音,图片… 3.大数据影响因素 大多快低 tb pb eb zb 1.硬件 2.网络带宽 4.大数据的特征 数据量…...

小鹏汽车Q1财报:押注G6、大力降本,明年智驾BOM降半
作者 | 德新编辑 | 王博 小鹏汽车本周发了Q1财报,数据不好看,以致于在微博端也发了公开信。 那后续呢? 小鹏第二季度指引是,总交付数量约为2.1 - 2.2万辆,收入预计约为45 - 47亿元;四季度,…...

VMware ESXi 8.0U1a 发布 - 领先的裸机 Hypervisor
VMware ESXi 8.0U1a 发布 - 领先的裸机 Hypervisor 请访问原文链接:https://sysin.org/blog/vmware-esxi-8-u1/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org 2023-06-01, VMware vSphere 8.0U1a 发布。 详见&am…...
Unity的IPreprocessBuild:深入解析与实用案例
Unity IPreprocessBuild Unity IPreprocessBuild是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自动执行一些操作。这个功能可以帮助开发者提高工作效率,减少手动操作的时间和错误率。在本文中我们将介绍Unity IPreprocessBuild的使用方…...

htmlCSS-----CSS选择器(下)
目录 前言: 2.高级选择器 (1)子代选择器 (2)伪类选择器 (3)后代选择器 (4)兄弟选择器 相邻兄弟选择器 通用兄弟选择器 (5)并集选择器 &am…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...