From 076b4fcb43a651917245b24f4e14e0859a1a228a Mon Sep 17 00:00:00 2001 From: tonyhcj Date: Fri, 17 Jul 2026 21:48:26 -0300 Subject: [PATCH] Adiciona gerenciamento de heartbeat e peers --- .../src/pitiupi/control/HeartbeatManager.java | 101 ++++++++++++++++++ pitiupi/src/pitiupi/control/PeerInfo.java | 40 +++++++ pitiupi/src/pitiupi/control/PeerListener.java | 10 ++ pitiupi/src/pitiupi/net/HeartbeatMessage.java | 23 ++++ 4 files changed, 174 insertions(+) create mode 100644 pitiupi/src/pitiupi/control/HeartbeatManager.java create mode 100644 pitiupi/src/pitiupi/control/PeerInfo.java create mode 100644 pitiupi/src/pitiupi/control/PeerListener.java create mode 100644 pitiupi/src/pitiupi/net/HeartbeatMessage.java diff --git a/pitiupi/src/pitiupi/control/HeartbeatManager.java b/pitiupi/src/pitiupi/control/HeartbeatManager.java new file mode 100644 index 0000000..b3a83a9 --- /dev/null +++ b/pitiupi/src/pitiupi/control/HeartbeatManager.java @@ -0,0 +1,101 @@ +package pitiupi.control; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import pitiupi.GUI.MainWindow; +import pitiupi.net.HeartbeatMessage; +/** + * + * @author tony + */ +public class HeartbeatManager { + + private static final long HEARTBEAT_INTERVAL_MS = 5000; + private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos + + private final MainWindow mainWindow; + private final Map peers = new ConcurrentHashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); + private ScheduledExecutorService scheduler; + + public HeartbeatManager(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + public void start() { + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(this::announce, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(this::checkTimeouts, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + public void stop() { + if (scheduler != null) { + scheduler.shutdownNow(); + } + } + + private void announce() { + try { + HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName()); + mainWindow.getSocket().send(msg.toByteArray()); + } catch (IOException ex) { + Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex); + } + } + + // Chamado pelo Socket quando um pacote é identificado como heartbeat + public void receiveHeartbeat(HeartbeatMessage msg, InetAddress from) { + // ignora o próprio heartbeat + if (msg.getUserName().equals(mainWindow.getUserName())) { + return; + } + + String key = from.getHostAddress() + "#" + msg.getUserName(); + boolean isNew = !peers.containsKey(key); + + PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp())); + peer.touch(System.currentTimeMillis()); + + if (isNew) { + notifyListeners(); + } + } + + private void checkTimeouts() { + long now = System.currentTimeMillis(); + boolean changed = peers.values().removeIf(p -> now - p.getLastSeen() > PEER_TIMEOUT_MS); + if (changed) { + notifyListeners(); + } + } + + private void notifyListeners() { + List snapshot = new ArrayList<>(peers.values()); + for (PeerListener l : listeners) { + l.onPeersChanged(snapshot); + } + } + + public void addPeerListener(PeerListener l) { + listeners.add(l); + l.onPeersChanged(new ArrayList<>(peers.values())); + } + + public void removePeerListener(PeerListener l) { + listeners.remove(l); + } + + public List getPeers() { + return new ArrayList<>(peers.values()); + } +} diff --git a/pitiupi/src/pitiupi/control/PeerInfo.java b/pitiupi/src/pitiupi/control/PeerInfo.java new file mode 100644 index 0000000..04e6bdd --- /dev/null +++ b/pitiupi/src/pitiupi/control/PeerInfo.java @@ -0,0 +1,40 @@ +package pitiupi.control; + +import java.net.InetAddress; +/** + * + * @author tony + */ +public class PeerInfo { + + private final String userName; + private final InetAddress address; + private volatile long lastSeen; + + public PeerInfo(String userName, InetAddress address, long lastSeen) { + this.userName = userName; + this.address = address; + this.lastSeen = lastSeen; + } + + public String getUserName() { + return userName; + } + + public InetAddress getAddress() { + return address; + } + + public long getLastSeen() { + return lastSeen; + } + + public void touch(long timestamp) { + this.lastSeen = timestamp; + } + + @Override + public String toString() { + return userName + " (" + address.getHostAddress() + ")"; + } +} diff --git a/pitiupi/src/pitiupi/control/PeerListener.java b/pitiupi/src/pitiupi/control/PeerListener.java new file mode 100644 index 0000000..1b97f8b --- /dev/null +++ b/pitiupi/src/pitiupi/control/PeerListener.java @@ -0,0 +1,10 @@ +package pitiupi.control; + +import java.util.List; +/** + * + * @author tony + */ +public interface PeerListener { + void onPeersChanged(List peers); +} diff --git a/pitiupi/src/pitiupi/net/HeartbeatMessage.java b/pitiupi/src/pitiupi/net/HeartbeatMessage.java new file mode 100644 index 0000000..f8cf549 --- /dev/null +++ b/pitiupi/src/pitiupi/net/HeartbeatMessage.java @@ -0,0 +1,23 @@ +package pitiupi.net; +/** + * + * @author tony + */ +public class HeartbeatMessage extends Message { + + private final String userName; + private final long timestamp; + + public HeartbeatMessage(String userName) { + this.userName = userName; + this.timestamp = System.currentTimeMillis(); + } + + public String getUserName() { + return userName; + } + + public long getTimestamp() { + return timestamp; + } +}