306 lines
11 KiB
C#
306 lines
11 KiB
C#
using System.Net.WebSockets;
|
||
using System.Text;
|
||
using Newtonsoft.Json;
|
||
|
||
namespace HeartRateMonitorAndroid.Services;
|
||
|
||
public class WebSocketService
|
||
{
|
||
// 心率上报
|
||
public class HeartRateWebSocketClient : IDisposable
|
||
{
|
||
private readonly string _serverUrl;
|
||
private ClientWebSocket _webSocket;
|
||
private CancellationTokenSource _cts;
|
||
private bool _isConnected = false;
|
||
private int _reconnectDelayMs = 5000; // 初始重连延时5秒
|
||
private readonly int _maxReconnectDelayMs = 60000; // 最大重连延时60秒
|
||
private readonly object _lockObject = new object();
|
||
private bool _isReconnecting = false; // 是否正在重连
|
||
private int _reconnectAttempts = 0; // 重连尝试次数
|
||
|
||
public HeartRateWebSocketClient(string serverUrl)
|
||
{
|
||
_serverUrl = serverUrl;
|
||
_webSocket = new ClientWebSocket();
|
||
|
||
// 配置WebSocket客户端选项,增强后台运行稳定性
|
||
_webSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
|
||
_webSocket.Options.SetBuffer(8192, 8192); // 增加缓冲区大小
|
||
|
||
// 在某些Android设备上,默认SubProtocol可能导致连接问题
|
||
// _webSocket.Options.AddSubProtocol("json"); // 可以根据服务器要求添加子协议
|
||
|
||
_cts = new CancellationTokenSource();
|
||
}
|
||
|
||
public async Task ConnectAsync()
|
||
{
|
||
if (_isConnected) return;
|
||
|
||
try
|
||
{
|
||
Console.WriteLine($"正在连接到 WebSocket 服务器: {_serverUrl}");
|
||
await _webSocket.ConnectAsync(new Uri(_serverUrl), _cts.Token);
|
||
|
||
_isConnected = true;
|
||
Console.WriteLine("已成功连接到 WebSocket 服务器");
|
||
|
||
// 连接成功,重置重连参数
|
||
_reconnectAttempts = 0;
|
||
_reconnectDelayMs = 5000; // 重置为初始值
|
||
|
||
// 启动接收消息的任务
|
||
_ = ReceiveMessagesAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"连接 WebSocket 服务器失败: {ex.Message}");
|
||
if (!_isReconnecting) // 只有在不是重连过程中才触发重连
|
||
{
|
||
await ReconnectAsync();
|
||
}
|
||
}
|
||
}
|
||
|
||
private async Task ReceiveMessagesAsync()
|
||
{
|
||
var buffer = new byte[4096];
|
||
try
|
||
{
|
||
while (_webSocket.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||
{
|
||
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
|
||
|
||
if (result.MessageType == WebSocketMessageType.Close)
|
||
{
|
||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||
_isConnected = false;
|
||
await ReconnectAsync();
|
||
break;
|
||
}
|
||
|
||
if (result.MessageType == WebSocketMessageType.Text)
|
||
{
|
||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||
Console.WriteLine($"收到服务器消息: {message}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"接收消息时出错: {ex.Message}");
|
||
_isConnected = false;
|
||
await ReconnectAsync();
|
||
}
|
||
}
|
||
|
||
private async Task ReconnectAsync()
|
||
{
|
||
if (_cts.IsCancellationRequested) return;
|
||
|
||
// 检查是否已经在重连中,避免多次重连
|
||
lock (_lockObject)
|
||
{
|
||
if (_isReconnecting)
|
||
{
|
||
Console.WriteLine("已经有重连任务在进行中,跳过此次重连请求");
|
||
return;
|
||
}
|
||
_isReconnecting = true;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 增加重连次数
|
||
_reconnectAttempts++;
|
||
|
||
// 使用指数退避策略增加等待时间
|
||
// 每次重连失败后,等待时间翻倍,但不超过最大值
|
||
_reconnectDelayMs = Math.Min(_reconnectDelayMs * 2, _maxReconnectDelayMs);
|
||
|
||
Console.WriteLine($"第{_reconnectAttempts}次重连尝试,等待{_reconnectDelayMs / 1000}秒...");
|
||
|
||
// 如果重连次数超过特定阈值,显示通知提醒用户
|
||
if (_reconnectAttempts == 3 || _reconnectAttempts == 5 || _reconnectAttempts % 10 == 0)
|
||
{
|
||
await ShowReconnectionNotification();
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
if (_webSocket.State != WebSocketState.Open && _webSocket.State != WebSocketState.Connecting)
|
||
{
|
||
_webSocket.Dispose();
|
||
_webSocket = new ClientWebSocket();
|
||
|
||
// 设置WebSocket选项以提高后台连接可靠性
|
||
_webSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
|
||
_webSocket.Options.SetBuffer(8192, 8192); // 增加缓冲区大小
|
||
}
|
||
}
|
||
|
||
await Task.Delay(_reconnectDelayMs);
|
||
await ConnectAsync();
|
||
|
||
// 连接成功,重置重连计数和延迟
|
||
if (_isConnected)
|
||
{
|
||
_reconnectAttempts = 0;
|
||
_reconnectDelayMs = 5000; // 重置为初始值
|
||
Console.WriteLine("重连成功,重置重连参数");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"重连过程中发生错误: {ex.Message}");
|
||
// 使用当前的延迟时间再次尝试
|
||
await Task.Delay(_reconnectDelayMs);
|
||
// 释放重连锁,允许下次重连
|
||
lock (_lockObject) { _isReconnecting = false; }
|
||
await ReconnectAsync();
|
||
}
|
||
finally
|
||
{
|
||
// 确保重连锁被释放
|
||
lock (_lockObject) { _isReconnecting = false; }
|
||
}
|
||
}
|
||
|
||
// 显示重连通知
|
||
private async Task ShowReconnectionNotification()
|
||
{
|
||
try
|
||
{
|
||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||
{
|
||
var title = "连接中断";
|
||
var message = $"服务器连接已断开,正在尝试第{_reconnectAttempts}次重连。";
|
||
|
||
// 使用应用程序的通知服务显示通知
|
||
HeartRateMonitorAndroid.Services.NotificationService.ShowReconnectionNotification(
|
||
title,
|
||
message,
|
||
_reconnectAttempts);
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"显示重连通知失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
public async Task SendHeartRateAsync(int heartRate)
|
||
{
|
||
var heartRateData = new HeartRateData
|
||
{
|
||
HeartRate = heartRate,
|
||
Timestamp = DateTime.Now,
|
||
DeviceName = "Android Heart Rate Monitor"
|
||
};
|
||
|
||
await SendHeartRateDataAsync(heartRateData);
|
||
}
|
||
|
||
public async Task SendHeartRateDataAsync(HeartRateData data)
|
||
{
|
||
// 检查WebSocket状态
|
||
if (_webSocket.State != WebSocketState.Open || !_isConnected)
|
||
{
|
||
Console.WriteLine($"WebSocket未连接,当前状态: {_webSocket.State},尝试重新连接");
|
||
_isConnected = false;
|
||
|
||
// 如果已经在重连过程中,不要再次尝试连接
|
||
if (!_isReconnecting)
|
||
{
|
||
await ConnectAsync();
|
||
}
|
||
|
||
if (!_isConnected)
|
||
{
|
||
Console.WriteLine("重连失败,无法发送数据");
|
||
|
||
// 如果重连次数超过阈值,显示连接失败通知
|
||
if (_reconnectAttempts >= 3 && !_isReconnecting)
|
||
{
|
||
await ShowReconnectionNotification();
|
||
// 触发重连
|
||
await ReconnectAsync();
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
var json = JsonConvert.SerializeObject(data);
|
||
var buffer = Encoding.UTF8.GetBytes(json);
|
||
|
||
// 设置发送超时
|
||
var sendCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
|
||
sendCts.CancelAfter(TimeSpan.FromSeconds(5)); // 5秒超时
|
||
|
||
await _webSocket.SendAsync(
|
||
new ArraySegment<byte>(buffer),
|
||
WebSocketMessageType.Text,
|
||
true,
|
||
sendCts.Token);
|
||
|
||
//Console.WriteLine($"成功发送心率数据: {data.HeartRate} bpm");
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
Console.WriteLine("发送数据超时");
|
||
_isConnected = false;
|
||
await ReconnectAsync();
|
||
}
|
||
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
|
||
{
|
||
Console.WriteLine("WebSocket连接已关闭,尝试重新连接");
|
||
_isConnected = false;
|
||
await ReconnectAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"发送心率数据失败: {ex.Message}, 类型: {ex.GetType().Name}");
|
||
_isConnected = false;
|
||
await ReconnectAsync();
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
_cts.Cancel();
|
||
if (_webSocket.State == WebSocketState.Open)
|
||
{
|
||
_webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端关闭", CancellationToken.None)
|
||
.Wait(TimeSpan.FromSeconds(2));
|
||
}
|
||
_webSocket.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"关闭WebSocket客户端时出错: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
public class HeartRateData
|
||
{
|
||
public int HeartRate { get; set; }
|
||
public DateTime Timestamp { get; set; }
|
||
public string DeviceName { get; set; }
|
||
public string Token
|
||
{
|
||
get
|
||
{
|
||
using var stream = FileSystem.OpenAppPackageFileAsync("token.txt").Result;
|
||
using var reader = new StreamReader(stream);
|
||
|
||
var contents = reader.ReadToEnd();
|
||
return contents;
|
||
}
|
||
}
|
||
}
|
||
} |