情境:
我正在使用将部署到一组设备的Xamarin.Forms构建Android应用程序。除了其中一个设备之外的所有设备都将进行一些数据收集,剩下的设备将是" hub"汇总所有数据并进行一些报告。我正在使用蓝牙进行设备到设备的通信。标记为主服务器的' hub充当客户端,并且所有收集器充当服务器。我有一个原型使用单个服务器和客户端...几乎。
有时,客户端/主服务器将无法从服务器/收集器读取。我正在努力找到原因,并希望得到任何帮助。
症状:
客户端对InputStream的.Read()调用偶尔会无限期地阻塞,即使服务器已写入输出流。我已为此次通话添加了超时,以防止该应用完全卡住。 这种情况间歇性地发生,但我发现了一些模式,当它工作时,它没有
代码:
所有蓝牙处理都是由我使用Xamarin.Forms DependencyService注入的单个类/服务完成的。所有设备在启动时(通过此类的构造函数)将在后台线程上无限循环,等待连接并重复。这个蓝牙代码很大程度上基于蓝牙聊天示例,以及我发现的一些其他在线资源(一些Android原生/ java,一些Xamarin / C#)
主人将根据需要(通过按下UI中的按钮触发)尝试连接到任何收集器(通过绑定的蓝牙设备)并从中读取数据。还有一个简单的UI组件,它基本上用作控制台日志。
这是完整的服务类。
public class GameDataSyncService : IGameDataSyncService
{
private const string UUID = "8e99f5f1-4a07-4268-9686-3a288326e0a2";
private static Task acceptLoopTask;
private static Task syncDataTask;
private static readonly object locker = new object();
private static bool running = false;
public event EventHandler<DataSyncMessage> MessageBroadcast;
public GameDataSyncService()
{
// Every device will listen and accept incoming connections. The master will make the connections.
lock (locker)
{
if (acceptLoopTask == null)
{
acceptLoopTask = Task.Factory.StartNew(AcceptLoopWorker, TaskCreationOptions.LongRunning);
}
}
}
public void SyncData()
{
lock (locker)
{
if (running)
{
BroadcastMessage("Previous data sync is still running.", DataSyncMessageType.Warning);
return;
}
else
{
running = true;
syncDataTask = Task.Factory.StartNew(SyncDataWorker);
}
}
}
private void BroadcastMessage(string message, DataSyncMessageType type = DataSyncMessageType.Info)
{
MessageBroadcast?.Invoke(this, new DataSyncMessage { Text = message, Type = type });
}
private async Task AcceptLoopWorker()
{
int count = 0;
while (true)
{
BluetoothServerSocket serverSocket = null;
BluetoothSocket clientSocket = null;
try
{
BroadcastMessage($"Listening for incoming connection...", DataSyncMessageType.Debug);
serverSocket = BluetoothAdapter.DefaultAdapter.ListenUsingRfcommWithServiceRecord(nameof(GameDataSyncService), Java.Util.UUID.FromString(UUID));
clientSocket = serverSocket.Accept(); // This call blocks until a connection is established.
BroadcastMessage($"Connection received from {clientSocket.RemoteDevice.Name}. Sending data...", DataSyncMessageType.Info);
var bytes = Encoding.UTF8.GetBytes($"Hello World - {string.Join(" ", Enumerable.Repeat(Guid.NewGuid(), ++count))}");
await clientSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length);
clientSocket.OutputStream.Flush();
// Give the master some time to close the connection from their end
await Task.Delay(1000*3);
}
catch (Exception ex)
{
BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
}
finally
{
try { clientSocket?.InputStream?.Close(); } catch { }
try { clientSocket?.InputStream?.Dispose(); } catch { }
try { clientSocket?.OutputStream?.Close(); } catch { }
try { clientSocket?.OutputStream?.Dispose(); } catch { }
try { clientSocket?.Close(); } catch { }
try { clientSocket?.Dispose(); } catch { }
try { serverSocket?.Close(); } catch { }
try { serverSocket?.Dispose(); } catch { }
BroadcastMessage($"Connection closed.", DataSyncMessageType.Debug);
}
}
}
private async Task SyncDataWorker()
{
BroadcastMessage($"Beginning data sync...");
foreach (var bondedDevice in BluetoothAdapter.DefaultAdapter.BondedDevices.OrderBy(d => d.Name))
{
BluetoothSocket clientSocket = null;
try
{
clientSocket = bondedDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString(UUID));
BroadcastMessage($"Connecting to {bondedDevice.Name}...");
try
{
clientSocket.Connect();
}
catch
{
BroadcastMessage($"Connection to {bondedDevice.Name} failed.", DataSyncMessageType.Error);
}
while (clientSocket.IsConnected)
{
byte[] buffer = new byte[1024];
var readTask = clientSocket.InputStream.ReadAsync(buffer, 0, buffer.Length);
if (await Task.WhenAny(readTask, Task.Delay(1000)) != readTask)
{
BroadcastMessage($"Read timeout...", DataSyncMessageType.Error);
break;
}
int bytes = readTask.Result;
BroadcastMessage($"Read {bytes} bytes.", DataSyncMessageType.Success);
if (bytes > 0)
{
var text = Encoding.UTF8.GetString(buffer.Take(bytes).ToArray());
BroadcastMessage(text, DataSyncMessageType.Success);
break;
}
}
}
catch (Exception ex)
{
BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
}
finally
{
try { clientSocket?.InputStream?.Close(); } catch { }
try { clientSocket?.InputStream?.Dispose(); } catch { }
try { clientSocket?.OutputStream?.Close(); } catch { }
try { clientSocket?.OutputStream?.Dispose(); } catch { }
try { clientSocket?.Close(); } catch { }
try { clientSocket?.Dispose(); } catch { }
}
}
await Task.Delay(1000 * 3);
BroadcastMessage($"Data sync complete!");
lock (locker)
{
running = false;
}
}
}
我尝试了什么(下面没有任何效果):
其中大部分来自“解决方案”。来自其他stackoverflow帖子。
视频
我拍了一段关于这件事的视频。 后面的平板电脑是收集器/服务器前台的平板电脑是主/客户端。视频启动时,客户端显示一些先前的尝试,服务器应用程序在后台(但正在运行)。我演示了.Read在收集器/服务器应用程序在后台时工作,但不在前台。每个开始数据同步的请求都有一个对应的&#34;控制台&#34; (或者如果我太快按下它就会发出警告) https://youtu.be/NGuGa7upCU4
要点:
据我所知,我的代码是正确的。我不知道还有什么可以改变/修复以使其更可靠地工作。实际连接似乎是成功的(基于来自服务器/收集器的日志,遗憾的是未在视频中显示),但问题出在.Write(或.Read)中。任何帮助,建议或见解都会很棒。
答案 0 :(得分:0)
尝试以下操作,将所有内容更改为使用:
Param(
[string]$collectionurl = "http://ictfs2015:8080/tfs/DefaultCollection",
[string]$projectName = "ProjectName",
[string]$BuildId = "44",
[string]$user = "username",
[string]$token = "password"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uri = "$($collectionurl)/$($projectName)/_apis/build/builds/$BuildId/timeline?api-version=2.0"
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
$steps = $response.records | where {$_.result -eq 'failed' -and $_.type -eq 'Task'} # Filter the failed steps
$failedsteps = @()
foreach($step in $steps){
$customObject = new-object PSObject -property @{
"StepId" = $step.id
"type" = $step.type
"TaskName" = $step.name
"startTime" = $step.startTime
"finishTime" = $step.finishTime
"state" = $step.state
"result" = $step.result
"changeId" = $step.changeId
"workerName" = $step.workerName
}
$failedsteps += $customObject
}
$failedsteps | Select `
StepId,
type,
TaskName,
startTime,
finishTime,
state,
result,
changeId,
workerName #|export-csv -Path C:\FailedBuildSteps.csv -NoTypeInformation