feat: 长保持连接,控制端可重启

This commit is contained in:
Mock
2026-04-10 11:11:03 +08:00
parent 2033db7268
commit 79dba2a664
10 changed files with 930 additions and 105 deletions

View File

@@ -67,7 +67,7 @@ static PyObject *build_recv_meta_dict(
static PyObject *build_stats_dict(const omnisocket_session_stats_t *stats) {
return Py_BuildValue(
"{s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:i}",
"{s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:i,s:i,s:s}",
"send_calls",
(unsigned long long) stats->send_calls,
"send_bytes",
@@ -83,7 +83,11 @@ static PyObject *build_stats_dict(const omnisocket_session_stats_t *stats) {
"recv_errors",
(unsigned long long) stats->recv_errors,
"connected",
stats->connected
stats->connected,
"registered",
stats->registered,
"last_server_error",
stats->last_server_error
);
}

View File

@@ -1,5 +1,33 @@
#include "omnisocket_client.h"
static void omnisocket_session_sync_client_state_locked(omnisocket_session_t *session, kcp_client_t *client) {
kcp_client_state_t client_state;
if (session == NULL) {
return;
}
memset(&client_state, 0, sizeof(client_state));
if (client != NULL) {
kcp_client_state_snapshot(client, &client_state);
}
session->stats.connected = client_state.connected;
session->stats.registered = client_state.registered;
snprintf(
session->stats.last_server_error,
sizeof(session->stats.last_server_error),
"%s",
client_state.last_server_error
);
}
static void omnisocket_session_mark_disconnected_locked(omnisocket_session_t *session) {
if (session == NULL) {
return;
}
session->stats.connected = 0;
session->stats.registered = 0;
}
int omnisocket_session_init(omnisocket_session_t *session) {
int rc;
@@ -97,7 +125,7 @@ int omnisocket_session_connect(
return -1;
}
session->client = client;
session->stats.connected = 1;
omnisocket_session_sync_client_state_locked(session, client);
pthread_mutex_unlock(&session->mutex);
return 0;
}
@@ -119,7 +147,7 @@ int omnisocket_session_close(omnisocket_session_t *session) {
session->closing = 1;
session->client = NULL;
}
session->stats.connected = 0;
omnisocket_session_mark_disconnected_locked(session);
pthread_mutex_unlock(&session->mutex);
if (client != NULL) {
@@ -158,6 +186,7 @@ int omnisocket_session_send(omnisocket_session_t *session, const char *to, const
} else {
session->stats.send_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
@@ -190,6 +219,7 @@ int omnisocket_session_recv(omnisocket_session_t *session, message_t *out_msg, i
} else {
session->stats.recv_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
@@ -228,6 +258,7 @@ int omnisocket_session_recv_into(
} else {
session->stats.recv_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
@@ -376,6 +407,8 @@ int omnisocket_udp_session_connect(
}
session->client = client;
session->stats.connected = 1;
session->stats.registered = 1;
session->stats.last_server_error[0] = '\0';
pthread_mutex_unlock(&session->mutex);
return 0;
}
@@ -398,6 +431,7 @@ int omnisocket_udp_session_close(omnisocket_udp_session_t *session) {
session->client = NULL;
}
session->stats.connected = 0;
session->stats.registered = 0;
pthread_mutex_unlock(&session->mutex);
if (client != NULL) {

View File

@@ -13,6 +13,8 @@ typedef struct omnisocket_session_stats {
uint64_t recv_timeouts;
uint64_t recv_errors;
int connected;
int registered;
char last_server_error[256];
} omnisocket_session_stats_t;
typedef struct omnisocket_session_kcp_stats {

View File

@@ -121,6 +121,8 @@ def test_control_sessions_smoke(transport: str, binary_name: str, session_cls) -
receiver_stats = receiver.stats()
assert sender_stats['connected'] == 1
assert receiver_stats['connected'] == 1
assert sender_stats['registered'] == 1
assert receiver_stats['registered'] == 1
assert sender_stats['send_calls'] >= 2
assert receiver_stats['recv_calls'] >= 2
if transport == 'kcp':
@@ -135,6 +137,63 @@ def test_control_sessions_smoke(transport: str, binary_name: str, session_cls) -
receiver.close()
def test_kcp_duplicate_peer_new_instance_wins() -> None:
port = _reserve_port()
listen_addr = f'127.0.0.1:{port}'
shared_peer_id = 'pytest-kcp-shared-peer'
sender_id = 'pytest-kcp-unique-sender'
with _run_server('kcpserver', listen_addr):
original = _connect_with_retry(Session, transport='kcp', server_addr=listen_addr, peer_id=shared_peer_id)
sender = _connect_with_retry(Session, transport='kcp', server_addr=listen_addr, peer_id=sender_id)
replacement = None
try:
replacement = _connect_with_retry(Session, transport='kcp', server_addr=listen_addr, peer_id=shared_peer_id)
replacement_stats = replacement.stats()
assert replacement_stats['connected'] == 1
assert replacement_stats['registered'] == 1
with pytest.raises(OSError):
original.recv(timeout_ms=1000)
payload = b'registered-replacement'
sender.send(to=shared_peer_id, data=payload)
from_peer, msg_type, recv_payload = replacement.recv(timeout_ms=1000)
assert from_peer == sender_id
assert msg_type == MSG_TYPE_BINARY
assert recv_payload == payload
finally:
original.close()
sender.close()
if replacement is not None:
replacement.close()
def test_kcp_idle_video_peers_survive_without_receive_loop() -> None:
port = _reserve_port()
listen_addr = f'127.0.0.1:{port}'
sender_id = 'peer-b-video'
receiver_id = 'peer-a-video'
with _run_server('kcpserver', listen_addr):
sender = _connect_with_retry(Session, transport='kcp', server_addr=listen_addr, peer_id=sender_id)
receiver = _connect_with_retry(Session, transport='kcp', server_addr=listen_addr, peer_id=receiver_id)
try:
time.sleep(5.0)
payload = b'idle-video-session-still-alive'
sender.send(to=receiver_id, data=payload)
from_peer, msg_type, recv_payload = receiver.recv(timeout_ms=1000)
assert from_peer == sender_id
assert msg_type == MSG_TYPE_BINARY
assert recv_payload == payload
finally:
sender.close()
receiver.close()
def test_udp_session_close_interrupts_blocking_recv() -> None:
port = _reserve_port()
listen_addr = f'127.0.0.1:{port}'