-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsocket_client.py
More file actions
104 lines (85 loc) · 3.18 KB
/
Copy pathsocket_client.py
File metadata and controls
104 lines (85 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import json
import socket
import time
HOST = "127.0.0.1"
PORT = 7878
RECONNECT_DELAY = 2.0
SOCKET_TIMEOUT = 5.0
def connect():
"""Si connette al TCP loopback del collector Rust su 127.0.0.1:7878.
Usa TCP su loopback invece di Unix Domain Socket per compatibilità
Windows nativa. La latenza aggiuntiva è irrilevante dato che il
collector campiona a 1 Hz.
Se la connessione fallisce (server non ancora partito), riprova
ogni RECONNECT_DELAY secondi finché non riesce.
"""
while True:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(SOCKET_TIMEOUT)
sock.connect((HOST, PORT))
print(f"[socket_client] connesso a {HOST}:{PORT}")
return sock
except (socket.error, ConnectionRefusedError) as e:
print(f"[socket_client] connessione fallita: {e}")
print(f"[socket_client] riprovo tra {RECONNECT_DELAY}s...")
time.sleep(RECONNECT_DELAY)
def read_lines(sock, bufsize=4096):
"""Generator che produce righe JSON complete da un socket.
PROBLEMA DEL FRAMING: i dati in un socket stream possono arrivare
in chunk arbitrari (es. metà JSON in un recv(), il resto nel prossimo).
La soluzione standard è bufferizzare finché non si trova il delimitatore
di riga (\n), che il server Rust invia dopo ogni JSON.
Se non arrivano dati entro SOCKET_TIMEOUT secondi, yield None
per dare al chiamante la possibilità di fare altre operazioni.
Quando il socket si chiude (server morto), il loop termina.
"""
buffer = b""
while True:
try:
data = sock.recv(bufsize)
except socket.timeout:
yield None
continue
if not data:
break
buffer += data
while b"\n" in buffer:
line, buffer = buffer.split(b"\n", 1)
if line:
yield line.decode("utf-8")
yield None
def run(on_metric, on_disconnect=None):
"""Ciclo principale: connetti, leggi, riconnetti in caso di caduta.
La connessione può cadere se:
- Il server Rust viene riavviato
- Il server crasha
- Timeout/reset di rete su loopback
In ogni caso, riconnettiamo automaticamente senza crashare.
"""
while True:
sock = connect()
try:
for line in read_lines(sock):
if line is None:
continue
try:
data = json.loads(line)
on_metric(data)
except json.JSONDecodeError as e:
print(f"[socket_client] JSON malformato: {e}")
except Exception as e:
print(f"[socket_client] errore di lettura: {type(e).__name__}: {e}")
finally:
try:
sock.close()
except Exception:
pass
if on_disconnect is not None:
try:
on_disconnect()
except Exception as e:
print(f"[socket_client] errore in on_disconnect: {e}")
print("[socket_client] connessione persa. Riconnessione tra "
f"{RECONNECT_DELAY}s...")
time.sleep(RECONNECT_DELAY)