From 203e5f6ed5cde0ff17ec9cabe5f12d4ddb061dae Mon Sep 17 00:00:00 2001 From: ua1zbe Date: Fri, 9 Oct 2026 13:05:05 +0300 Subject: [PATCH] =?UTF-8?q?reconnect:=20=D0=BD=D0=B5=20=D1=83=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D1=82=D1=8C=20=D0=B2=20=C2=AB=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BA=D1=83=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=C2=BB=20=D0=BF=D1=80=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=BE=D0=BC=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D1=8B=D0=B2=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После короткого обрыва сервер ещё держит старую сессию и на рукопожатии присылает MSG_ERROR. Клиент трактовал любую ошибку до STATE_READY как фатальную (stopFatal → STATE_AUTH_FAILED) и прекращал переподключения. Теперь ошибки рукопожатия (MSG_ERROR, протокольные сбои, слишком большой кадр) считаются временными, если успешная сессия уже была (everReady): показываем STATE_ERROR и продолжаем автопереподключение. Фатально — только до первой успешной авторизации (реально неверные креды). --- .../ua1zbe/svxremote/proto/ReflectorClient.kt | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ru/ua1zbe/svxremote/proto/ReflectorClient.kt b/app/src/main/java/ru/ua1zbe/svxremote/proto/ReflectorClient.kt index b08ca3e..080f1df 100644 --- a/app/src/main/java/ru/ua1zbe/svxremote/proto/ReflectorClient.kt +++ b/app/src/main/java/ru/ua1zbe/svxremote/proto/ReflectorClient.kt @@ -85,6 +85,15 @@ class ReflectorClient( @Volatile private var fatal = false + /** + * Была ли хотя бы одна успешная сессия за текущее подключение. + * Если да — креды уже проверены, поэтому ошибки на рукопожатии считаем + * временными (сервер мог ещё держать старую сессию после обрыва) и + * продолжаем автопереподключение вместо показа «ошибки авторизации». + */ + @Volatile + private var everReady = false + @Volatile private var tcpSocket: Socket? = null private var udpSocket: DatagramSocket? = null @@ -107,6 +116,7 @@ class ReflectorClient( this.settings = settings manualDisconnect.set(false) fatal = false + everReady = false if (running.get()) { // Перезапуск с новыми настройками (например, смена профиля) running.set(false) @@ -399,6 +409,7 @@ class ReflectorClient( serverCodecs = info.codecs callbacks.onServerInfo(info) state = STATE_READY + everReady = true callbacks.onState(STATE_READY) // Сначала сообщаем о себе (версия ПО для дашборда), затем // выбираем TG для передачи и подписываемся на прослушивание @@ -441,9 +452,17 @@ class ReflectorClient( ReflectorProtocol.MSG_ERROR -> { val err = MsgParser.errorMessage(msg.fields) - Log.w(TAG, "MSG_ERROR: $err") + Log.w(TAG, "MSG_ERROR: $err (state=$state, everReady=$everReady)") if (state < STATE_READY) { - stopFatal(err.ifEmpty { "Access denied" }) + if (everReady) { + // Креды проверены ранее: ошибка на рукопожатии почти + // всегда временная (сервер ещё держит старую сессию + // после короткого обрыва) — переподключаемся заново. + callbacks.onState(STATE_ERROR, err.ifEmpty { "Переподключение" }) + abortSockets() + } else { + stopFatal(err.ifEmpty { "Access denied" }) + } return false } callbacks.onError(err) @@ -452,7 +471,13 @@ class ReflectorClient( else -> Unit // неизвестные сообщения игнорируются } } catch (e: Exception) { - stopFatal("Ошибка протокола: ${e.message}") + if (everReady) { + // Сбой после успешного подключения — не фатально, переподключаемся. + callbacks.onState(STATE_ERROR, "Ошибка протокола: ${e.message}") + abortSockets() + } else { + stopFatal("Ошибка протокола: ${e.message}") + } return false } return true @@ -478,7 +503,12 @@ class ReflectorClient( } private fun protocolError(detail: String): Boolean { - stopFatal(detail) + if (everReady) { + callbacks.onState(STATE_ERROR, detail) + abortSockets() + } else { + stopFatal(detail) + } return false }