简述
本文讲述如何使用32feet.NET实现Bluetooth的广播程序,同时演示了Broadcom stack在Windows Mobilie下的实现。
背景
在.NET Compact Framework下的Bluetooth开发 之 32feet.NET 的反馈中 camper9999 同学希望实现蓝牙广播的功能,本文就是一个基于32feet.NET蓝牙广播的实现。
sammylp 的提出代码挂死问题,其实是使用过程的不恰当造成的,本文演示如何使用线程防止UI线程的挂起,程序的假死。
另外一个同学(不好意思忘记哪位了)问32feet.NET是否支持Broadcom stack,所以本文的实现运行于安装Broadcom stack的windows mobile中。
感谢各位的反馈,现在尽量在一篇文章中回答。
关于Bluetooth开发的也可以参考以下其他文章:
.NET Compact Framework下的Bluetooth开发 之 Windows Embedded Source Tools for Bluetooth
.NET Compact Framework下的Bluetooth开发 之 32feet.NET
.NET Compact Framework下的Bluetooth开发 之 Bluetooth Virtual Serial Port
.NET Compact Framework下的Bluetooth设备的配对
30 Days of .NET [Windows Mobile Applications] - Day 02: Bluetooth Manager(蓝牙管理器)
什么是广播
所谓广播就是消息发送方向公众(public)发送信息的过程,广播有一个主要的特点是消息发送方不需要知道消息接收方的存在。现实生活中广播的例子如收音机广播,GPS卫星广播,以太网同网段数据包的广播等等。可是所谓蓝牙广播其实不算严格下的广播,因为蓝牙通信过程中有发现,配对,甚至验证过程,所以通信双方是需要握手的,没办法实现严格意义上的广播。本文例子实现了一个通过注册订阅方式的组播过程(MultiCast)。
实现
服务端
服务端负责监听和注册服务,同时把消息发送到已经注册的设备去。在例子中服务端使用PC实现,其实可以使用Windows Mobilie作为服务端,32feet.net库基本兼容PC和CE。
成员定义
private BluetoothListener listener;
private bool listening = true;
private List<BluetoothClient> clientList = new List<BluetoothClient>();
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()
{
List<BluetoothClient> tempClientList = new List<BluetoothClient>();
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);
}
}
}
平台:Visual Studio 2008 + Windows Mobile 5 Packet PC SDK