【Linux C】基于树莓派/香橙派的蓝牙服务端——支持多蓝牙设备接入

1 篇文章 0 订阅
1 篇文章 0 订阅

一、需求

在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(从机),允许外来设备(主机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为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

在这里插入图片描述

可以看到已经可以实现多客户端蓝牙通信;

局限性

未考虑多并发的情况,所以代码可以引入互斥量、条件变量等极致,防止因为并发导致的数据不准确

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
摘 要:基于对Linux蓝牙协议栈BlueZ 源代码的分析,给出BlueZ的组织结构和特点。分析蓝牙USB 传输驱动机制和数据处理过程, 给出实现蓝牙设备驱动的重要数据结构和流程,并总结Linux 下开发蓝牙USB 设备驱动的一般方法和关键技术。 关键词:Linux 系统;蓝牙协议栈;设备驱动 USB Device Driver for Linux Bluetooth Stack LIANG Jun-xue, YU Bin (Institute of Electronic Technology, PLA Information Engineering University, Zhengzhou 450004) 【Abstract】This paper depicts the structure and characteristics of BlueZ based on analyzing the source code of Linux bluetooth stack BlueZ. It analyzes the implementation of bluetooth USB transport driver scheme and data processing procedure in detail, and gives the key data structure and implementation of bluetooth device driver. It summarizes the approach of developing Linux bluetooth USB device driver and the key technology. 【Key words】Linux system; bluetooth stack; device driver 计 算 机 工 程 Computer Engineering 第 34 卷 第 9 期 Vol.34 No.9 2008 年 5 月 May 2008 ·开发研究与设计技术· 文章编号:1000—3428(2008)09—0273—03 文献标识码:A 中图分类号:TP391 1 概述 蓝牙技术是开放式通信规范,而 Linux 是开放源码的操 作系统。廉价设备与免费软件的结合,促进了蓝牙技术和 Linux 的发展与融合。 Linux最早的蓝牙协议栈是由Axis Communication Inc在 1999 年发布的 OpenBT 协议栈。 随后, IBM 发布了 BlueDrekar 协议栈,但没有公开其源码。Qualcomm Incorporated 在 2001 年发布的 BlueZ 协议栈被接纳为 2.4.6 内核的一部分。此外, Rappore Technology 及 Nokia 的 Affix Bluetooth Stack 都是 Linux 系统下的蓝牙协议栈,应用在不同的设备和领域中。 BlueZ 是 Linux 的官方蓝牙协议栈,也是目前应用最广 泛的协议栈,几乎支持所有已通过认证的蓝牙设备。对于基 于主机的蓝牙应用,目前常见的硬件接口有 UART, USB 和 PC 卡等,USB 作为 PC 的标准外设接口,具有连接方便、兼 容性好和支持高速设备等特点,已广泛应用于蓝牙设备。 目前对 Linux 下 USB 设备驱动的研究已较为广泛而深 入[1-4] ,但对 Linux 下的蓝牙设备驱动还没有专门的研究。本 文在分析 USB 设备驱动和蓝牙协议栈的基础上,总结了 Linux 下开发蓝牙 USB 驱动程序的一般方法,并深入剖析了 其关键技术。 2 Linux 蓝牙协议栈 BlueZ 简介 BlueZ 目前已成为一个开放性的源码工程。它可以很好 地在 Linux 支持的各种体系的硬件平台下运行,包括各种单 处理器平台、多处理器平台及超线程系统。 BlueZ 由多个独立的模块组成,内核空间主要包括设备 驱动层、蓝牙核心及 HCI 层、L2CAP 与 SCO 音频层、 RFCOMM, BNEP, CMTP 与 HIDP 层、通用蓝牙 SDP 库和后 台服务及面向所有层的标准套接字接口;在用户空间提供了 蓝牙配置、测试及协议分析等工具。其组织结构如图 1 所示, BlueZ 没有实现专门的 SDP 层,而是将其实现为运行在后台 的蓝牙服务库例程(图 1 没有描述该后台服务)。 RFOMM 层支 持标准的套接口,并提供了串行仿真 TTY 接口,这使串行端 口应用程序和协议可以不加更改地运行在蓝牙设备上,例如 通过点对点协议 PPP 可实现基于 TCP/IP 协议簇的所有网络 应用。BNEP 层实现了蓝牙的以太网仿真,TCP/IP 可以直接 运行于其上。 USB设备驱动 (hci_usb.o) L2CAP层(l2cap.o) RFCOMM层 (rfcomm.o) BNEP层 (bnep.o) CMTP层 (cmtp.o) 串口设备驱动 (hci_uart.o) 虚拟串口设备驱动 (hci_vhci.o) 音频 socket RFCOMM socket BNEP socket CMTP socket L2CAP socket HCI socket 内核 空间 用户 空间 串口设备 CAPI设备 输入设备 网络设备 HDIP socket 音频设备 AF_BLUETOOTH socket 音频层(sco.o) PPP TCP/IP AF_INET socket BNEP层 (bnep.o) 其他设备驱动 (bluecard_cs.o等) BlueZ工具和实用程序 HDIP层 (hdip.o) BlueZ核心 及HCI层(bluez.o/bluetooth.o) 图 1 BlueZ 组织结构 3 蓝牙 USB 设备驱动 设备驱动程序在 Linux 内核中起着重要作用,它使某个 硬件能响应一个定义良好的内部编程接口。这些接口隐藏了 设备的工作细节,用户通过一组独立于特定驱动程序的标准 调用来操作设备。而将这些调用映射到作用于实际硬件设备 的特有操作上,则是驱动程序的任务。
要使用libevent库实现TCP/IP客户端和服务端通信,可以按照以下步骤进行: 1. 引入libevent库的头文件和链接库。 ```c #include <event2/event.h> #include <event2/bufferevent.h> #include <event2/buffer.h> #include <event2/listener.h> #include <event2/util.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <fcntl.h> #pragma comment(lib, "event.lib") #pragma comment(lib, "event_core.lib") #pragma comment(lib, "event_extra.lib") ``` 2. 创建服务端监听器并设置回调函数。 ```c struct event_base* base = event_base_new(); // 创建event_base对象 // 创建监听器 struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(8888); struct evconnlistener* listener = evconnlistener_new_bind(base, accept_cb, NULL, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, 10, (struct sockaddr*)&sin, sizeof(sin)); // 设置回调函数 void accept_cb(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* addr, int len, void* ptr) { struct event_base* base = evconnlistener_get_base(listener); struct bufferevent* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL); bufferevent_enable(bev, EV_READ | EV_WRITE); } ``` 3. 创建客户端并连接到服务端。 ```c struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(8888); inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr); // 创建事件对象 struct event_base* base = event_base_new(); struct bufferevent* bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL); // 连接服务端 bufferevent_socket_connect(bev, (struct sockaddr*)&sin, sizeof(sin)); ``` 4. 设置读写回调函数。 ```c void read_cb(struct bufferevent* bev, void* ctx) { struct evbuffer* input = bufferevent_get_input(bev); size_t len = evbuffer_get_length(input); char* data = (char*)malloc(len + 1); memset(data, 0, len + 1); evbuffer_copyout(input, data, len); printf("recv: %s\n", data); free(data); evbuffer_drain(input, len); } void event_cb(struct bufferevent* bev, short events, void* ctx) { if (events & BEV_EVENT_EOF) { printf("connection closed\n"); } else if (events & BEV_EVENT_ERROR) { printf("some other error\n"); } bufferevent_free(bev); } ``` 5. 启动事件循环。 ```c event_base_dispatch(base); ``` 这样就可以使用libevent库实现TCP/IP客户端和服务端通信了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值