using System.Collections.Concurrent; using System.Net.Sockets; using ErvProtocol; namespace ErvCollector.Storage { // 현장별 활성 소켓 + 최신 상태 보관. 제어 프레임을 해당 현장 EW11 소켓으로 송신. public sealed class SiteHub { readonly ConcurrentDictionary _sockets = new(); readonly ConcurrentDictionary _last = new(); readonly ConcurrentDictionary _lastSeenUtc = new(); public event Action? Log; public void SetSocket(string site, NetworkStream s) { _sockets[site] = s; } public void RemoveSocket(string site, NetworkStream s) { // 현재 등록된 것이 이 스트림일 때만 제거(재연결 레이스 방지) if (_sockets.TryGetValue(site, out var cur) && ReferenceEquals(cur, s)) _sockets.TryRemove(site, out _); } public void SetStatus(string site, StatusRecord rec) { _last[site] = rec; _lastSeenUtc[site] = DateTime.UtcNow; } public StatusRecord? GetStatus(string site) => _last.TryGetValue(site, out var r) ? r : null; public DateTime LastSeen(string site) => _lastSeenUtc.TryGetValue(site, out var t) ? t : DateTime.MinValue; public bool IsOnline(string site) => _sockets.ContainsKey(site) && (DateTime.UtcNow - LastSeen(site)) < TimeSpan.FromSeconds(30); // 제어 프레임 송신. 성공 true. public bool TrySend(string site, byte[] frame) { if (!_sockets.TryGetValue(site, out var stream)) { Log?.Invoke($"[{site}] 제어 실패: 연결 없음"); return false; } try { lock (stream) { stream.Write(frame, 0, frame.Length); stream.Flush(); } return true; } catch (Exception ex) { Log?.Invoke($"[{site}] 제어 송신 오류: {ex.Message}"); return false; } } } }