简述
本文讲述如何使用32feet.NET实现Bluetooth的广播程序,同时演示了Broadcom stack在Windows Mobilie下的实现。
背景
在 的反馈中 同学希望实现蓝牙广播的功能,本文就是一个基于32feet.NET蓝牙广播的实现。
的提出代码挂死问题,其实是使用过程的不恰当造成的,本文演示如何使用线程防止UI线程的挂起,程序的假死。
另外一个同学(不好意思忘记哪位了)问32feet.NET是否支持Broadcom stack,所以本文的实现运行于安装Broadcom stack的windows mobile中。
感谢各位的反馈,现在尽量在一篇文章中回答。
关于Bluetooth开发的也可以参考以下其他文章:
什么是广播
所谓广播就是消息发送方向公众(public)发送信息的过程,广播有一个主要的特点是消息发送方不需要知道消息接收方的存在。现实生活中广播的例子如收音机广播,GPS卫星广播,以太网同网段数据包的广播等等。可是所谓蓝牙广播其实不算严格下的广播,因为蓝牙通信过程中有发现,配对,甚至验证过程,所以通信双方是需要握手的,没办法实现严格意义上的广播。本文例子实现了一个通过注册订阅方式的组播过程(MultiCast)。
实现
服务端
服务端负责监听和注册服务,同时把消息发送到已经注册的设备去。在例子中服务端使用PC实现,其实可以使用Windows Mobilie作为服务端,32feet.net库基本兼容PC和CE。
成员定义
private BluetoothListener listener; private bool listening = true; private ListclientList = new List (); private System.Threading.Thread listenThread; private System.Threading.Thread broadcastThread;
listener负责监听服务,clientList 存放已经注册的设备,listenThread负责监听的线程,broadcastThread负责广播的线程。
启动服务
BluetoothRadio radio = BluetoothRadio.PrimaryRadio; if (radio == null) { WriteMessage("No radio hardware or unsupported software stack"); return; } // Enable discoverable mode radio.Mode = RadioMode.Discoverable; WriteMessage("Radio Name:" + radio.Name); WriteMessage("Radio Address:" + radio.LocalAddress); WriteMessage("Radio Mode now: " + radio.Mode.ToString()); listener = new BluetoothListener(BluetoothService.SerialPort); listener.Start(); listening = true; listenThread = new System.Threading.Thread(ListenLoop); broadcastThread = new System.Threading.Thread(BroadcastLoop); listenThread.Start(); broadcastThread.Start(); WriteMessage("Service started!");
启动服务的流程是:
1.检查蓝牙设备是否准备好。
2.设置蓝牙设备为可发现。
3.启动蓝牙监听,这里配置的服务类型为串口服务,在客户端也需要配置串口服务类型才能进行通信。
4.启动监听线程,这样不会挂死主线程(Main Thread)。
5.启动广播线程。
监听线程
private void ListenLoop() { byte[] buffer = new byte[4]; string dataToSend = "Thanks for subscription"; byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(dataToSend); while (listening) { try { BluetoothClient client = listener.AcceptBluetoothClient(); WriteMessage("Get a subscription from " + client.RemoteMachineName); clientList.Add(client); System.IO.Stream ns = client.GetStream(); ns.Write(dataBuffer, 0, dataBuffer.Length); } catch { break; } } listener.Stop(); }
监听线程负责处理监听订阅请求,并把订阅的设备增加到订阅列表中。AcceptBluetoothClient()会挂起改线程,直到有新的设备进行订阅。
广播线程
private void BroadcastLoop() { ListtempClientList = new List (); while (listening) { System.Threading.Thread.Sleep(5000); string dataToSend = "Broadcast Message at " + System.DateTime.Now.ToLongTimeString(); byte[] dataBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(dataToSend); tempClientList.Clear(); foreach (BluetoothClient client in clientList) { try { System.IO.Stream ns = client.GetStream(); ns.Write(dataBuffer, 0, dataBuffer.Length); WriteMessage("Sent message to " + client.RemoteMachineName); } catch { //connection is broken. tempClientList.Add(client); continue; } } //clean up the broken connections. foreach (BluetoothClient client in tempClientList) { clientList.Remove(client); } } }
广播线程负责对已经订阅的设备进行消息广播,同时管理已经断开的链接。在实际应用中,这个线程需要根据需求来更改业务流程。
关闭服务
WriteMessage("Service stop!"); listening = false; if (listener != null) { listener.Stop(); }
释放监听资源。
UI处理
由于使用了多线程,不能直接更新UI,所以需要借助delegate和Invoke()函数来更新。
public delegate void SafeWinFormsThreadDelegate(string msg); private void WriteMessage(string msg) { SafeWinFormsThreadDelegate d = new SafeWinFormsThreadDelegate(UpdateUi); Invoke(d, new object[] { msg }); } private void UpdateUi(string msg) { if (listBoxMsg.Items.Count > 100) { listBoxMsg.Items.RemoveAt(0); } listBoxMsg.SelectedIndex = listBoxMsg.Items.Add(msg); }
客户端
客户端负责发现服务端设备,同时发起订阅请求,然后接收广播消息。客户端使用安装了BroadCom stack的Windows Mobile实现,实际上同时支持MS stack。
发现
BluetoothRadio radio = BluetoothRadio.PrimaryRadio; if (radio == null) { WriteMessage("No radio hardware or unsupported software stack"); return; } //Broadcom stack doesn't support the functionality to turn on the bluetooth, turn it on manually please //radio.Mode = RadioMode.Connectable; //Scan the nearby devices listBoxDevices.Items.Clear(); BluetoothDeviceInfo[] devices = client.DiscoverDevices(); listBoxDevices.DataSource = devices; listBoxDevices.DisplayMember = "DeviceName"; listBoxDevices.ValueMember = "DeviceAddress"; WriteMessage("Discover successful, please select one device to subscribe.");
由于当前版本的32feet.net在BroadCom stack下不支持设置蓝牙状态,所以如果设备是BroadCom stack需要屏蔽设置蓝牙状态的语句。把发现到的设备显示到ListBox里面。
订阅
BluetoothAddress deviceAddress = listBoxDevices.SelectedValue as BluetoothAddress; client.Connect(deviceAddress, BluetoothService.SerialPort); WriteMessage("Connected to " + client.RemoteMachineName); stream = client.GetStream(); receiving = true; System.Threading.Thread t = new System.Threading.Thread(ReceiveLoop); t.Start();
根据发现的服务端设备的地址进行连接,然后启动线程接收消息。
接收消息
private void ReceiveLoop() { byte[] buffer = new byte[255]; while (receiving) { if (stream.CanRead) { stream.Read(buffer, 0, 255); string data = System.Text.ASCIIEncoding.ASCII.GetString(buffer, 0, 255); WriteMessage(data); } } }
在线程里接收消息,避免主UI线程挂死。
源代码:
平台:Visual Studio 2008 + Windows Mobile 5 Packet PC SDK