当前位置:   article > 正文

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

linux 蓝牙编程

一、需求

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

二、环境准备

bluez安装

linux C在终端中输入以下命令,安装BlueZ库:

sudo apt-get update
sudo apt-get install bluez
sudo apt-get install libbluetooth-dev
  • 1
  • 2
  • 3
修改配置文件

修改 /etc/systemd/system/dbus-org.bluez.service

在ExecStart =/usr/lib/Bluetooth/bluetoothd 后面添加-C
紧接着添加一行:ExecStartPost=/usr/bin/sdptool add SP
  • 1
  • 2

其中修改系统中蓝牙服务的启动选项,-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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
蓝牙命令行操作(非必须)

如果想改变蓝牙的配置或查询状态等,可以通过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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186

编译语句

将BluetoothServer2.c编译为可执行文件BluetoothServer2

gcc -o BluetoothServer2 BluetoothServer2.c -lbluetooth -lpthread
  • 1

执行结果

开启服务端后,分别用两台手机的蓝牙接入服务端,并向服务端发送消息;然后服务端再广播消息到两台设备上

服务端结果
在这里插入图片描述
手机蓝牙1
在这里插入图片描述

手机蓝牙2

在这里插入图片描述

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

局限性

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/483710
推荐阅读
相关标签
  

闽ICP备14008679号