feat: TCP Server 自动重启机制 — 3种超时条件

触发条件(任一满足即重启):
1. 5分钟无任何连接 (从未连或上次连接后超时)
2. 5分钟无数据交互 (已连接但静默)
3. Auth 密码错误3次

重启流程: 关闭socket→清状态→重新SocketCreat+SocketListen
保护机制: 最多连续重启3次, 超限后进入10分钟冷却期

其他改动:
- tcp_json_srv_init 去掉 _init_done 限制, 支持重新初始化
- 新增 tcp_json_restart() 函数
- 新增 g_json_last_comm_ts / g_json_last_connect_ts 追踪
- CONNECT 时清零 restart_count (正常连接重置计数器)
This commit is contained in:
wangfq
2026-07-03 11:46:17 +08:00
parent d9059fac92
commit 5966eb8657
2 changed files with 119 additions and 26 deletions
@@ -22,6 +22,9 @@
#define TCP_JSON_RECV_BUF_LEN (TCP_JSON_MAX_FRAME * 2)
#define TCP_JSON_MAX_PWD_RETRY 3 // Max password retries before lockout
#define TCP_JSON_LOCKOUT_TIMEOUT_MS 60000 // 60s lockout after max retries
#define TCP_JSON_IDLE_TIMEOUT_MS 300000 // 5min idle → restart (no connect / no comm)
#define TCP_JSON_MAX_RESTART_COUNT 3 // Max auto-restart count
#define TCP_JSON_RESTART_COOLDOWN_MS 600000 // 10min cooldown after max restarts
/*===========================================================================
* Auth / State
@@ -63,6 +66,10 @@ extern TcpJsonAuthState g_json_auth_state;
extern uint32_t g_json_auth_timer; // ms timer for auth timeout
extern uint8_t g_json_pwd_retry; // password retry counter
extern TcpJsonPending g_json_pending; // Pending Loop MCU command state
extern uint32_t g_json_last_comm_ts; // Last communication timestamp (ms)
extern uint32_t g_json_last_connect_ts; // Last client connect timestamp (ms)
extern uint8_t g_json_restart_count; // Auto-restart counter
extern uint32_t g_json_restart_cooldown; // Cooldown deadline (ms)
/*===========================================================================
* API
@@ -71,5 +78,6 @@ void tcp_json_srv_init(void); // Create listen socket on port
void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket interrupt handler
void tcp_json_poll(void); // Periodic poll (auth timeout, etc.)
void tcp_json_push_sensor(void); // Push sensor data (0xC0) to TCP client
void tcp_json_restart(void); // Close + reopen socket (auto-restart)
#endif /* __TCP_JSON_SRV_H__ */
@@ -32,6 +32,10 @@ uint8_t g_json_pwd_retry = 0;
uint32_t g_json_lockout_timer = 0;
TcpJsonPending g_json_pending = {0, 0, 0, "", 0};
uint8_t g_report_active = 0; // 0=不主动上报, 1=主动上报传感器数据
uint32_t g_json_last_comm_ts = 0; // 最后通信时间戳
uint32_t g_json_last_connect_ts = 0; // 最后连接时间戳
uint8_t g_json_restart_count = 0; // 自动重启计数
uint32_t g_json_restart_cooldown = 0; // 冷却期截止时间
/*===========================================================================
* Frame Receive Buffer (line-delimited JSON)
@@ -196,11 +200,12 @@ static void handle_pwd_verify(uint8_t socket, uint32_t msg_id, const char *json)
} else {
g_json_pwd_retry++;
if (g_json_pwd_retry >= TCP_JSON_MAX_PWD_RETRY) {
g_json_auth_state = JSON_STATE_LOCKOUT;
g_json_lockout_timer = mstick();
PRINT("JSON: Auth locked out (%d retries)\n", g_json_pwd_retry);
PRINT("JSON: Auth failed %d times → restart\n", g_json_pwd_retry);
// 先发错误再重启,让 TCP 栈有时间 flush
json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL,
"password incorrect, locked out for 60s");
"password incorrect, connection will restart");
Delay_Ms(100);
tcp_json_restart();
} else {
json_send_error(socket, msg_id, "pwd_verify", JSON_CODE_AUTH_FAIL, "password incorrect");
}
@@ -788,36 +793,68 @@ static void json_sensor_callback(const uint8_t *pkg, uint16_t len)
* Public API
*===========================================================================*/
void tcp_json_restart(void) {
PRINT("JSON: === auto-restart (count=%d) ===\n", g_json_restart_count);
/* 1. 关闭数据 socket 和监听 socket */
if (g_json_socket_listen != 0xFF) {
WCHNET_SocketClose(g_json_socket_listen + 1, TCP_CLOSE_NORMAL);
WCHNET_SocketClose(g_json_socket_listen, TCP_CLOSE_NORMAL);
g_json_socket_listen = 0xFF;
}
/* 2. 重置状态 */
g_json_auth_state = JSON_STATE_WAIT_AUTH;
g_json_pwd_retry = 0;
g_json_recv_len = 0;
g_json_last_comm_ts = 0;
g_json_last_connect_ts = 0;
g_json_auth_timer = 0;
g_json_lockout_timer = 0;
g_json_pending.active = 0;
/* 3. 重新创建 socket 并监听 */
tcp_json_srv_init();
}
#define TCP_JSON_SRV_INIT_MAX_RETRY 3
void tcp_json_srv_init(void) {
uint8_t ret;
SOCK_INF sock_inf;
static uint8_t _init_done = 0;
uint8_t attempt;
if (_init_done) return; // 防止 net_srv_init 反复调用
_init_done = 1;
for (attempt = 0; attempt < TCP_JSON_SRV_INIT_MAX_RETRY; attempt++) {
memset(&sock_inf, 0, sizeof(SOCK_INF));
sock_inf.SourPort = TCP_JSON_PORT;
sock_inf.ProtoType = PROTO_TYPE_TCP;
sock_inf.RecvBufLen = RECE_BUF_LEN;
// 注册传感器回调 — 必须在 socket 操作之前,
// 避免 socket 失败导致 return 而漏掉注册
lup_set_sensor_callback(json_sensor_callback);
ret = WCHNET_SocketCreat(&g_json_socket_listen, &sock_inf);
if (ret != WCHNET_ERR_SUCCESS) {
PRINT("JSON: SocketCreat attempt %d failed: 0x%02X\n", attempt + 1, ret);
Delay_Ms(100);
continue;
}
memset(&sock_inf, 0, sizeof(SOCK_INF));
sock_inf.SourPort = TCP_JSON_PORT;
sock_inf.ProtoType = PROTO_TYPE_TCP;
sock_inf.RecvBufLen = RECE_BUF_LEN;
ret = WCHNET_SocketListen(g_json_socket_listen);
if (ret != WCHNET_ERR_SUCCESS) {
PRINT("JSON: SocketListen attempt %d failed: 0x%02X\n", attempt + 1, ret);
WCHNET_SocketClose(g_json_socket_listen, TCP_CLOSE_NORMAL);
g_json_socket_listen = 0xFF;
Delay_Ms(100);
continue;
}
ret = WCHNET_SocketCreat(&g_json_socket_listen, &sock_inf);
if (ret != WCHNET_ERR_SUCCESS) {
PRINT("JSON: SocketCreat failed: 0x%02X\n", ret);
// 注册传感器回调(幂等,重复调用无副作用)
lup_set_sensor_callback(json_sensor_callback);
g_json_auth_timer = mstick(); // 记录 listen 创建时刻
PRINT("JSON: TCP listen on port %d (socket %d)\n", TCP_JSON_PORT, g_json_socket_listen);
return;
}
ret = WCHNET_SocketListen(g_json_socket_listen);
if (ret != WCHNET_ERR_SUCCESS) {
PRINT("JSON: SocketListen failed: 0x%02X\n", ret);
return;
}
PRINT("JSON: TCP listen on port %d (socket %d)\n", TCP_JSON_PORT, g_json_socket_listen);
PRINT("JSON: SocketCreat/Listen failed after %d retries\n", TCP_JSON_SRV_INIT_MAX_RETRY);
}
void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) {
@@ -828,6 +865,9 @@ void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) {
g_json_pwd_retry = 0;
g_json_auth_timer = mstick();
g_json_recv_len = 0;
g_json_last_connect_ts = mstick();
g_json_last_comm_ts = mstick();
g_json_restart_count = 0; // 有连接就清零重启计数
memset(g_json_recv_buf, 0, sizeof(g_json_recv_buf));
PRINT("JSON: Client connected on socket %d, recv buf configured\n", socketid);
}
@@ -841,6 +881,7 @@ void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) {
uint32_t rd_len = recv_len;
uint8_t tmp_buf[RECE_BUF_LEN];
WCHNET_SocketRecv(socketid, tmp_buf, &rd_len);
g_json_last_comm_ts = mstick(); // 有数据交互
memcpy(g_json_recv_buf + g_json_recv_len, tmp_buf, (uint16_t)rd_len);
g_json_recv_len += (uint16_t)rd_len;
@@ -1138,7 +1179,38 @@ void tcp_json_poll(void) {
// Only poll if we have an active connection (socket configured)
if (g_json_socket_listen == 0xFF) return;
// Auth timeout check
// === 自动重启冷却期检查 ===
if (g_json_restart_cooldown > 0) {
if (mstick() < g_json_restart_cooldown) {
// 冷却期内,不做任何操作
return;
}
g_json_restart_cooldown = 0;
g_json_restart_count = 0;
PRINT("JSON: restart cooldown expired, resetting counter\n");
}
// === 超时条件1: 5分钟无连接 ===
if (g_json_last_connect_ts == 0) {
// 从未连接过 — 用 auth_timer (listen 创建时设) 作为起点
if (g_json_auth_state == JSON_STATE_WAIT_AUTH
&& mstick() - g_json_auth_timer > TCP_JSON_IDLE_TIMEOUT_MS) {
PRINT("JSON: %dmin no connection → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000);
goto do_restart;
}
} else if (mstick() - g_json_last_connect_ts > TCP_JSON_IDLE_TIMEOUT_MS) {
PRINT("JSON: %dmin since last connect → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000);
goto do_restart;
}
// === 超时条件2: 5分钟无通信 (仅在有连接/已认证时检查) ===
if (g_json_last_comm_ts > 0
&& mstick() - g_json_last_comm_ts > TCP_JSON_IDLE_TIMEOUT_MS) {
PRINT("JSON: %dmin no communication → restart\n", TCP_JSON_IDLE_TIMEOUT_MS / 60000);
goto do_restart;
}
// Auth timeout check (existing)
if (g_json_auth_state == JSON_STATE_WAIT_AUTH) {
if (mstick() - g_json_auth_timer > TCP_JSON_AUTH_TIMEOUT_MS) {
PRINT("JSON: Auth timeout, closing connection\n");
@@ -1149,7 +1221,7 @@ void tcp_json_poll(void) {
}
}
// Lockout timeout check
// Lockout timeout check (existing)
if (g_json_auth_state == JSON_STATE_LOCKOUT) {
if (mstick() - g_json_lockout_timer > TCP_JSON_LOCKOUT_TIMEOUT_MS) {
PRINT("JSON: Lockout expired\n");
@@ -1160,6 +1232,19 @@ void tcp_json_poll(void) {
// Check for pending Loop MCU deferred responses
json_check_pending();
return;
do_restart:
g_json_restart_count++;
g_json_restart_cooldown = 0;
if (g_json_restart_count > TCP_JSON_MAX_RESTART_COUNT) {
// 超过最大重启次数,进入冷却期
g_json_restart_cooldown = mstick() + TCP_JSON_RESTART_COOLDOWN_MS;
PRINT("JSON: max restart (%d) reached, cooldown %d min\n",
TCP_JSON_MAX_RESTART_COUNT, TCP_JSON_RESTART_COOLDOWN_MS / 60000);
} else {
tcp_json_restart();
}
}
/*===========================================================================