fix: 连接状态判定修复 — 收到首个数据包后才标记已连接,CONNECTING 5秒超时回退
- ProtocolClient: 仅收到首个有效数据包后设置 CONNECTED,不再 socket 创建后立即标记 - ProtocolClient: timeoutCheckLoop 在 connect() 中立即启动,覆盖 CONNECTING(5s) 和 CONNECTED(3s) - RobotConnection: 允许 CONNECTING → TIMEOUT 合法转换 - MainScreen: CONNECTING 时点击连接按钮仅断开回到 DISCONNECTED,不再自动重连 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -89,9 +89,10 @@ class ProtocolClient(
|
||||
/**
|
||||
* 建立连接:
|
||||
* 1. 创建 UDP socket
|
||||
* 2. 启动接收协程
|
||||
* 2. 启动接收/发送协程
|
||||
* 3. 发送订阅请求
|
||||
* 4. 启动心跳和断线检测
|
||||
* 4. 启动断线检测(CONNECTING 5s / CONNECTED 3s 无数据 → TIMEOUT)
|
||||
* 5. 首次收到有效数据包后标记已连接,并启动心跳 1Hz(在接收循环中处理)
|
||||
*/
|
||||
fun connect() {
|
||||
if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return
|
||||
@@ -131,14 +132,7 @@ class ProtocolClient(
|
||||
// 发送订阅请求(触发服务端 UDP 推送)
|
||||
sendSubscriptionRequests()
|
||||
|
||||
_state.value = State.CONNECTED
|
||||
|
||||
// 启动心跳
|
||||
heartbeatJob = scope.launch(Dispatchers.IO) {
|
||||
heartbeatLoop(sock, address)
|
||||
}
|
||||
|
||||
// 启动断线检测
|
||||
// 启动断线检测(立即开始,覆盖 CONNECTING 和 CONNECTED 状态)
|
||||
timeoutJob = scope.launch {
|
||||
timeoutCheckLoop()
|
||||
}
|
||||
@@ -232,6 +226,14 @@ class ProtocolClient(
|
||||
if (report != null) {
|
||||
_statusReport.tryEmit(report)
|
||||
}
|
||||
|
||||
// 首次收到有效数据包:标记已连接,启动心跳
|
||||
if (_state.value == State.CONNECTING) {
|
||||
_state.value = State.CONNECTED
|
||||
heartbeatJob = scope.launch(Dispatchers.IO) {
|
||||
heartbeatLoop(sock, remoteAddress!!)
|
||||
}
|
||||
}
|
||||
} catch (_: SocketTimeoutException) {
|
||||
// 超时正常,继续循环
|
||||
continue
|
||||
@@ -258,12 +260,22 @@ class ProtocolClient(
|
||||
}
|
||||
}
|
||||
|
||||
/** 断线检测:3 秒无数据 → 标记 TIMEOUT */
|
||||
/** 断线检测:CONNECTED 状态 3 秒无数据 → TIMEOUT;CONNECTING 状态 5 秒无数据 → TIMEOUT */
|
||||
private suspend fun CoroutineScope.timeoutCheckLoop() {
|
||||
while (isActive && _state.value != State.DISCONNECTED) {
|
||||
val elapsed = System.currentTimeMillis() - lastPacketTimeMs
|
||||
if (elapsed > 3000 && _state.value == State.CONNECTED) {
|
||||
_state.value = State.TIMEOUT
|
||||
when (_state.value) {
|
||||
State.CONNECTED -> {
|
||||
if (elapsed > 3000) {
|
||||
_state.value = State.TIMEOUT
|
||||
}
|
||||
}
|
||||
State.CONNECTING -> {
|
||||
if (elapsed > 5000) {
|
||||
_state.value = State.TIMEOUT
|
||||
}
|
||||
}
|
||||
else -> { /* DISCONNECTED/TIMEOUT/ERROR: 无需超时处理 */ }
|
||||
}
|
||||
delay(500)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class RobotConnection {
|
||||
ProtocolClient.State.CONNECTING ->
|
||||
to == ProtocolClient.State.CONNECTED ||
|
||||
to == ProtocolClient.State.ERROR ||
|
||||
to == ProtocolClient.State.TIMEOUT ||
|
||||
to == ProtocolClient.State.DISCONNECTED
|
||||
ProtocolClient.State.CONNECTED ->
|
||||
to == ProtocolClient.State.TIMEOUT ||
|
||||
|
||||
@@ -172,7 +172,9 @@ fun MainScreen(
|
||||
onStatusClick = { statusPanelVisible = true },
|
||||
onSettingsClick = onSettingsClick,
|
||||
onConnectToggle = {
|
||||
if (connectionState == ProtocolClient.State.CONNECTED) {
|
||||
if (connectionState == ProtocolClient.State.CONNECTED ||
|
||||
connectionState == ProtocolClient.State.CONNECTING
|
||||
) {
|
||||
viewModel.disconnect()
|
||||
} else {
|
||||
viewModel.connect()
|
||||
|
||||
Reference in New Issue
Block a user