我正在开发两个独立的应用程序,用于使用 Obex 文件传输协议通过蓝牙 RFCOMM 进行数据传输。一方面,在 PC 上运行的 Windows C# 控制台应用程序侦听传入的蓝牙连接,并在客户端发出请求时发送图像。另一方面,移动设备上运行的 Android 应用程序会扫描附近的蓝牙设备,查找服务器并接收图像。
在大多数情况下,一切正常,图像传输没有问题。有时 - 不是很常见,我仍然不知道如何重现错误 - 图像在传输过程中被损坏,因为从 Android 应用程序接收到的一些字节与原始缓冲区不匹配(我计算了接收缓冲区并与原始缓冲区进行比较以检查图像是否已发送成功)。
这是一个例子:
Original
,
Received
这种“故障”图像只是一个例子,每次出现问题时,接收到的图像都会有不同的“故障效果”。
我试图解决这个问题的几件事:
- 更改 UUID,但 OOP UUID 和自定义 UUID 似乎都不起作用,因为出现了完全相同的问题。
- 我运行客户端应用程序的智能手机(小米红米 Note 8T)的内部存储可用空间几乎为零,因此我绝望地尝试释放一些内存,看看是否由于某种原因导致了错误(是的,它没有没有多大意义,但值得一提)。起初它有效,我认为以某种方式解决了问题,但随后错误又像以前一样出现了。
- 使用ACK系统来控制从服务器发送到客户端的每个数据子数组,类似于:PC发送第一个数据子数组,然后等待智能手机发送ACK以确认子数组的接收,并且只有在此之后,它才会继续发送下一个数据子数组,依此类推,直到缓冲区末尾。不用说,这个选项都不起作用(同样的错误和损坏的数据)。
- 我还尝试查看尝试连接到我的智能手机的其他设备是否会导致该问题,但事实并非如此。
CODE
服务器端
这是我在中的监听器的实现C# 控制台应用程序在 Windows 10 上运行。我拿了该服务器示例作为参考。
// Initialize the provider for the hosted RFCOMM service
_provider = await RfcommServiceProvider.CreateAsync(
RfcommServiceId.ObexFileTransfer); // Use Obex FTP protocol
// UUID is 00001106-0000-1000-8000-00805F9B34FB
// Create a listener for this service and start listening
StreamSocketListener listener = new StreamSocketListener();
listener.ConnectionReceived += OnConnectionReceivedAsync;
await listener.BindServiceNameAsync(
_provider.ServiceId.AsString(),
SocketProtectionLevel
.BluetoothEncryptionAllowNullAuthentication);
// Set the SDP attributes and start advertising
InitializeServiceSdpAttributes(_provider);
_provider.StartAdvertising(listener);
InitializeServiceSdpAttributes
功能:
const uint SERVICE_VERSION_ATTRIBUTE_ID = 0x0300;
const byte SERVICE_VERSION_ATTRIBUTE_TYPE = 0x0A; // UINT32
const uint SERVICE_VERSION = 200;
void InitializeServiceSdpAttributes(RfcommServiceProvider provider)
{
Windows.Storage.Streams.DataWriter writer = new Windows.Storage.Streams.DataWriter();
// First write the attribute type
writer.WriteByte(SERVICE_VERSION_ATTRIBUTE_TYPE);
// Then write the data
writer.WriteUInt32(MINIMUM_SERVICE_VERSION);
IBuffer data = writer.DetachBuffer();
provider.SdpRawAttributes.Add(SERVICE_VERSION_ATTRIBUTE_ID, data);
}
每当检测到新的连接尝试时,OnConnectionReceivedAsync
函数停止广告,处理监听器并创建一个新的StreamSocket
目的。此时,我设置输入和输出流,将图像转换为字节数组,并通过套接字将缓冲区长度发送到远程设备。一旦 Android 应用程序收到缓冲区的长度,它就会发送一个 ACK,这意味着它已准备好接收实际数据。
// Create input and output stream
DataWriter writer = new DataWriter(_socket.OutputStream);
// Convert image to array of bytes
byte[] imageByteArray;
using (var inputStream = await file.OpenSequentialReadAsync())
{
var readStream = inputStream.AsStreamForRead();
imageByteArray = new byte[readStream.Length];
await readStream.ReadAsync(imageByteArray, 0, imageByteArray.Length);
}
// Write length of data
writer.WriteBytes(intToByteArray(imageByteArray.Length));
await writer.StoreAsync();
// Wait for ACK ...
最后,我发送图像:
// Write bytes and send
writer.WriteBytes(imageByteArray);
await writer.StoreAsync();
// Wait for ACK ...
发送图像后,应用程序会在收到所有数据后收到来自远程设备的 ACK,然后关闭连接。
客户端
首先,安卓应用程序创建一个BluetoothSocket
使用从服务器应用程序指定的相同 UUID 的对象:
// Scan devices and find the remote Server by specifying the target MAC address
// ...
// targetDevice is the Server device
BluetoothSocket socket = targetDevice.createInsecureRfcommSocketToServiceRecord(
UUID.fromString("00001106-0000-1000-8000-00805F9B34FB") // FTP
);
// Connect the server
socket.connect();
最后,它从套接字读取传入的数据InputStream
。首先,它读取传入缓冲区的长度并发送 ACK 以确认已准备好接收图像。然后等待每个子数组,直到所有缓冲区都完成。此时,它发送最终的 ACK 并关闭连接。
// Get input stream
InputStream inputStream = socket.getInputStream();
// Buffer that contains the incoming data
byte[] buffer = null;
// The numOfBytes is the expected length of the buffer
int numOfBytes = 0;
// Index of the sub array within the complete buffer
int index = 0;
// flag is true if the receiver is computing the number of bytes that it has to receive
// flag is false if the receiver is actually reading the image sub arrays from the stream
int flag = true;
while(true){
// Estimate number of incoming bytes
if(flag){
try{
// inputStream.available() estimates the number of bytes that can be read
byte[] temp = new byte[inputStream.available()];
// Read the incoming data and store it in byte array temp (returns > 0 if successful)
if(inputStream.read(temp) > 0){
// Get length of expected data as array and parse it to an Integer
String lengthString = new String(temp, StandardCharsets.UTF_8);
numOfBytes = Integer.parseInt(lengthString);
// Create buffer
buffer = new byte[numOfBytes];
// Set the flag to false (turn on read image mode)
flag = false;
// Send ACK
}
}
catch (IOException e){
// ...
}
}
// Read image sub arrays
else {
try{
byte[] data = new byte[inputStream.available()];
// Read sub array and store it
int numbers = inputStream.read(data);
if(numbers <= 0 && index < numOfBytes)
continue;
// Copy sub array into the full image byte array
System.arraycopy(data, 0, buffer, index, numbers);
// Update index
index = index + numbers;
// Reached the end of the buffer (received all the data)
if(index == numOfBytes){
// Send ACK (Transfer success)
// ...
// Decode buffer and create image from byte array
Bitmap bmp = BitmapFactory.decodeByteArray(buffer, 0, numOfBytes);
// Store output image
outputImage = bmp;
// Dismiss the bluetooth manager (close socket, exit waiting loop...)
dismiss();
// Return the image
return bmp;
}
}
catch (IOException e){
// ...
}
}
}