我自己找到了解决方案,所以它是这样的:
首先,请记住定义蓝牙所有必要的声明和功能。这将明确关注代码部分。
对于 Xamarin/Android 客户端部分。该网站非常有帮助是这个吗 https://brianpeek.com/connect-to-a-bluetooth-device-with-xamarinandroid/。还可以尝试一下相当知名的聊天样本 https://developer.xamarin.com/samples/monodroid/BluetoothChat/对于 Xamarin.CreateMessage
是一种在本地设备上创建可显示的调试消息的方法。我保持非常简单,因为我的项目主要是关于 UWP 部分。所有这些都包含在一个try { } catch { }
子句,但由于有更多的缩进和括号,我现在将其省略。
using Java.Util;
using System.Text;
using System.IO;
using Android.Runtime;
using System.Threading.Tasks;
TestClass
{
// The UUIDs will be displayed down below if not known.
const string TARGET_UUID = "00001105-0000-1000-8000-00805f9b34fb";
BluetoothSocket socket = null;
OutputStreamInvoker outStream = null;
InputStreamInvoker inStream = null;
void Connect ()
{
BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
if (adapter == null) CreateMessage ("No Bluetooth adapter found.");
else if (!adapter.IsEnabled) CreateMessage ("Bluetooth adapter is not enabled.");
List<BluetoothDevice> L = new List<BluetoothDevice> ();
foreach (BluetoothDevice d in adapter.BondedDevices)
{
CreateMessage ("D: " + d.Name + " " + d.Address + " " + d.BondState.ToString ());
L.Add (d);
}
BluetoothDevice device = null;
device = L.Find (j => j.Name == "PC-NAME");
if (device == null) CreateMessage ("Named device not found.");
else
{
CreateMessage ("Device has been found: " + device.Name + " " + device.Address + " " + device.BondState.ToString ());
}
socket = device.CreateRfcommSocketToServiceRecord (UUID.FromString (TARGET_UUID));
await socket.ConnectAsync ();
if (socket != null && socket.IsConnected) CreateMessage ("Connection successful!");
else CreateMessage ("Connection failed!");
inStream = (InputStreamInvoker) socket.InputStream;
outStream = (OutputStreamInvoker) socket.OutputStream;
if (socket != null && socket.IsConnected)
{
Task t = new Task (() => Listen (inStream));
t.Start ();
}
else throw new Exception ("Socket not existing or not connected.");
}
}
现在我们进入字节和痛苦的部分。我使用这种格式来传输消息:[4 bytes of uint for message length] [1 byte per character]
。重要的是您使用相同的字节到 uint 转换,因为字节顺序 https://en.wikipedia.org/wiki/Endianness或总体情况在 UWP 特定方法中存在差异。如果你的字长不是它应该的样子(而不是~23,比如3000000+),那就是一个问题。读取尚不存在的字节可能意味着异常,甚至无情的崩溃,尽管使用try { } catch { }
条款。
以下方法以上述格式发送消息。如前所述,这是最简单的方法之一,所以我不会提及如何完成事情better.
private async void SendMessage (string message)
{
uint messageLength = (uint) message.Length;
byte[] countBuffer = BitConverter.GetBytes (messageLength);
byte[] buffer = Encoding.UTF8.GetBytes (message);
await outStream.WriteAsync (countBuffer, 0, countBuffer.Length);
await outStream.WriteAsync (buffer, 0, buffer.Length);
}
用法:运行方法 1,然后运行方法 2。您还可以在方法 1 最后(当已连接时)执行 SendMessage。
现在到关于监听消息/响应的部分。在第一种方法中,您将看到这个方法是通过任务运行的,因此它不会阻止它启动的方法。也许有 Xamarin/Android 特定的方法可以解决这个问题,但这对我来说并不重要,所以我只是绕过了这个问题。
private async void Listen (Stream inStream)
{
bool Listening = true;
CreateMessage ("Listening has been started.");
byte[] uintBuffer = new byte[sizeof (uint)]; // This reads the first 4 bytes which form an uint that indicates the length of the string message.
byte[] textBuffer; // This will contain the string message.
// Keep listening to the InputStream while connected.
while (Listening)
{
try
{
// This one blocks until it gets 4 bytes.
await inStream.ReadAsync (uintBuffer, 0, uintBuffer.Length);
uint readLength = BitConverter.ToUInt32 (uintBuffer, 0);
textBuffer = new byte[readLength];
// Here we know for how many bytes we are looking for.
await inStream.ReadAsync (textBuffer, 0, (int) readLength);
string s = Encoding.UTF8.GetString (textBuffer);
CreateMessage ("Received: " + s);
}
catch (Java.IO.IOException e)
{
CreateMessage ("Error: " + e.Message);
Listening = false;
break;
}
}
CreateMessage ("Listening has ended.");
}
这只是工作的一半。对于 UWP 服务器部分,我将简单地发布我的current代码,更加干净,不需要编辑。
using System;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth.Rfcomm;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
namespace BT
{
public sealed class BluetoothConnectionHandler
{
RfcommServiceProvider provider;
bool isAdvertising = false;
StreamSocket socket;
StreamSocketListener socketListener;
DataWriter writer;
DataReader reader;
Task listeningTask;
public bool Listening { get; private set; }
// I use Actions for transmitting the output and debug output. These are custom classes I created to pack them more conveniently and to be able to just "Trigger" them without checking anything. Replace this with regular Actions and use their invoke methods.
public ActionSingle<string> MessageOutput { get; private set; } = new ActionSingle<string> ();
public ActionSingle<string> LogOutput { get; private set; } = new ActionSingle<string> ();
// These were in the samples.
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0a; // UINT32
const uint SERVICE_VERSION = 200;
const bool DO_RESPONSE = true;
public async void StartServer ()
{
// Initialize the provider for the hosted RFCOMM service.
provider = await RfcommServiceProvider.CreateAsync (RfcommServiceId.ObexObjectPush);
// Create a listener for this service and start listening.
socketListener = new StreamSocketListener ();
socketListener.ConnectionReceived += OnConnectionReceived;
await socketListener.BindServiceNameAsync (provider.ServiceId.AsString (), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
// Set the SDP attributes and start advertising.
InitializeServiceSdpAttributes (provider);
provider.StartAdvertising (socketListener);
isAdvertising = true;
}
public void Disconnect ()
{
Listening = false;
if (provider != null) { if (isAdvertising) provider.StopAdvertising (); provider = null; } // StopAdvertising relentlessly causes a crash if not advertising.
if (socketListener != null) { socketListener.Dispose (); socketListener = null; }
if (writer != null) { writer.DetachStream (); writer.Dispose (); writer = null; }
if (reader != null) { reader.DetachStream (); reader.Dispose (); reader = null; }
if (socket != null) { socket.Dispose (); socket = null; }
if (listeningTask != null) { listeningTask = null; }
}
public async void SendMessage (string message)
{
// There's no need to send a zero length message.
if (string.IsNullOrEmpty (message)) return;
// Make sure that the connection is still up and there is a message to send.
if (socket == null || writer == null) { LogOutput.Trigger ("Cannot send message: No clients connected."); return; } // "No clients connected, please wait for a client to connect before attempting to send a message."
uint messageLength = (uint) message.Length;
byte[] countBuffer = BitConverter.GetBytes (messageLength);
byte[] buffer = Encoding.UTF8.GetBytes (message);
LogOutput.Trigger ("Sending: " + message);
writer.WriteBytes (countBuffer);
writer.WriteBytes (buffer);
await writer.StoreAsync ();
}
private void InitializeServiceSdpAttributes (RfcommServiceProvider provider)
{
DataWriter w = new DataWriter ();
// First write the attribute type.
w.WriteByte (SERVICE_VERSION_ATTRIBUTE_TYPE);
// Then write the data.
w.WriteUInt32 (SERVICE_VERSION);
IBuffer data = w.DetachBuffer ();
provider.SdpRawAttributes.Add (SERVICE_VERSION_ATTRIBUTE_ID, data);
}
private void OnConnectionReceived (StreamSocketListener listener, StreamSocketListenerConnectionReceivedEventArgs args)
{
provider.StopAdvertising ();
isAdvertising = false;
provider = null;
listener.Dispose ();
socket = args.Socket;
writer = new DataWriter (socket.OutputStream);
reader = new DataReader (socket.InputStream);
writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
//StartListening ();
LogOutput.Trigger ("Connection established.");
listeningTask = new Task (() => StartListening ());
listeningTask.Start ();
// Notify connection received.
}
private async void StartListening ()
{
LogOutput.Trigger ("Starting to listen for input.");
Listening = true;
while (Listening)
{
try
{
// Based on the protocol we've defined, the first uint is the size of the message. [UInt (4)] + [Message (1*n)] - The UInt describes the length of the message.
uint readLength = await reader.LoadAsync (sizeof (uint));
// Check if the size of the data is expected (otherwise the remote has already terminated the connection).
if (!Listening) break;
if (readLength < sizeof (uint))
{
Listening = false;
Disconnect ();
LogOutput.Trigger ("The connection has been terminated.");
break;
}
uint messageLength = reader.RReadUint (); //
LogOutput.Trigger ("messageLength: " + messageLength.ToString ());
// Load the rest of the message since you already know the length of the data expected.
readLength = await reader.LoadAsync (messageLength);
// Check if the size of the data is expected (otherwise the remote has already terminated the connection).
if (!Listening) break;
if (readLength < messageLength)
{
Listening = false;
Disconnect ();
LogOutput.Trigger ("The connection has been terminated.");
break;
}
string message = reader.ReadString (messageLength);
MessageOutput.Trigger ("Received message: " + message);
if (DO_RESPONSE) SendMessage ("abcdefghij");
}
catch (Exception e)
{
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus (e.HResult) == SocketErrorStatus.Unknown)
{
Listening = false;
Disconnect ();
LogOutput.Trigger ("Fatal unknown error occurred.");
break;
}
}
}
LogOutput.Trigger ("Stopped to listen for input.");
}
}
}
用法如下:
- 创建BluetoothConnectionHandler 的实例。
- 设置 MessageOutput 和/或 LogOutput(阅读代码中与此相关的注释)。
- 运行其 StartServer 方法。
- 要发送消息,请使用其 SendMessage 方法。
RReadUint的扩展方法:
public static uint RReadUint (this DataReader reader)
{
uint a = 0;
byte[] x = new byte[sizeof (uint)];
reader.ReadBytes (x);
a = BitConverter.ToUInt32 (x, 0);
return a;
}
这应该包含完成我要求的一切所需的一切......事后看来,我发现没有简单的答案可能。从现在开始一切可以得到改进,因为它可能是 UWP 和 Xamarin/Android 之间进行蓝牙通信的最基本方式。
如果您对此有疑问,请随时在评论部分提问。