Adiciona gerenciamento de heartbeat e peers

This commit is contained in:
tonyhcj 2026-07-17 21:48:26 -03:00
parent d22b95e74f
commit 076b4fcb43
4 changed files with 174 additions and 0 deletions

View File

@ -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<String, PeerInfo> peers = new ConcurrentHashMap<>();
private final List<PeerListener> 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<PeerInfo> 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<PeerInfo> getPeers() {
return new ArrayList<>(peers.values());
}
}

View File

@ -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() + ")";
}
}

View File

@ -0,0 +1,10 @@
package pitiupi.control;
import java.util.List;
/**
*
* @author tony
*/
public interface PeerListener {
void onPeersChanged(List<PeerInfo> peers);
}

View File

@ -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;
}
}