heartbeat

This commit is contained in:
mlus 2025-10-14 13:45:47 +08:00
parent b0850d8922
commit 73d426f266
4 changed files with 114 additions and 36 deletions

View File

@ -6,7 +6,6 @@ import net.minecraft.commands.Commands;
import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.RegisterCommandsEvent;
import vip.fubuki.playersync.sync.chat.ChatSyncClient;
@Mod.EventBusSubscriber() @Mod.EventBusSubscriber()
public class CommandInit { public class CommandInit {
@ -18,7 +17,6 @@ public class CommandInit {
.requires(cs->cs.hasPermission(2)) .requires(cs->cs.hasPermission(2))
.then(Commands.literal("reconnect") .then(Commands.literal("reconnect")
.executes(context -> { .executes(context -> {
new ChatSyncClient().run();
// context.getSource().sendSuccess(()->MutableComponent.create(new TranslatableContents("playersync.command.reconnect")),true); // context.getSource().sendSuccess(()->MutableComponent.create(new TranslatableContents("playersync.command.reconnect")),true);
return 0; return 0;
} }

View File

@ -1,6 +1,16 @@
package vip.fubuki.playersync; package vip.fubuki.playersync;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModList;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.config.JdbcConfig;
import vip.fubuki.playersync.sync.ChatSync; import vip.fubuki.playersync.sync.ChatSync;
@ -11,17 +21,6 @@ import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModList;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.fml.javafmlmod.FMLJavaModLoadingContext;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import net.neoforged.bus.api.IEventBus;
@Mod(PlayerSync.MODID) @Mod(PlayerSync.MODID)
public class PlayerSync { public class PlayerSync {
@ -181,4 +180,9 @@ public class PlayerSync {
LOGGER.info("PlayerSync is ready!"); LOGGER.info("PlayerSync is ready!");
} }
@SubscribeEvent
public void onServerStopping(ServerStoppingEvent event){
ChatSync.shutdown();
}
} }

View File

@ -8,10 +8,8 @@ import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.PlayerSync;
import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.config.JdbcConfig;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException; import java.net.ConnectException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@ -26,6 +24,9 @@ public class ChatSyncClient {
private static final int RECONNECT_DELAY = 5000; private static final int RECONNECT_DELAY = 5000;
private static final int MAX_RECONNECT_ATTEMPTS = 10; private static final int MAX_RECONNECT_ATTEMPTS = 10;
private static volatile long lastHeartbeat = System.currentTimeMillis();
private static final long HEARTBEAT_INTERVAL = 15000;
public void run() { public void run() {
int reconnectAttempts = 0; int reconnectAttempts = 0;
@ -36,40 +37,52 @@ public class ChatSyncClient {
JdbcConfig.CHAT_SERVER_PORT.get()); JdbcConfig.CHAT_SERVER_PORT.get());
clientSocket = new Socket(); clientSocket = new Socket();
clientSocket.setReuseAddress(true);
clientSocket.setKeepAlive(true);
clientSocket.setTcpNoDelay(true);
clientSocket.connect( clientSocket.connect(
new InetSocketAddress( new InetSocketAddress(
JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_IP.get(),
JdbcConfig.CHAT_SERVER_PORT.get() JdbcConfig.CHAT_SERVER_PORT.get()
), ),
10000 15000
); );
clientSocket.setSoTimeout(30000); clientSocket.setSoTimeout(30000);
out = new PrintWriter(clientSocket.getOutputStream(), true); out = new PrintWriter(new BufferedWriter(
BufferedReader in = new BufferedReader( new OutputStreamWriter(clientSocket.getOutputStream())), true);
new InputStreamReader(clientSocket.getInputStream()));
PlayerSync.LOGGER.info("Successfully connected to chat server"); PlayerSync.LOGGER.info("Successfully connected to chat server");
reconnectAttempts = 0; reconnectAttempts = 0;
lastHeartbeat = System.currentTimeMillis();
startHeartbeatMonitor();
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
String serverMessage; String serverMessage;
while (running && (serverMessage = in.readLine()) != null) { while (running && (serverMessage = in.readLine()) != null) {
lastHeartbeat = System.currentTimeMillis();
if ("<heartbeat>".equals(serverMessage)) {
continue;
}
PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage); PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage);
Component textComponents = Component.nullToEmpty(serverMessage); Component textComponents = Component.nullToEmpty(serverMessage);
if(playerList != null){ if(playerList != null){
if (playerList.getServer().isSameThread()) { playerList.getServer().execute(() ->
playerList.broadcastSystemMessage(textComponents, false); playerList.broadcastSystemMessage(textComponents, false));
} else {
playerList.getServer().execute(() ->
playerList.broadcastSystemMessage(textComponents, false));
}
} }
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
PlayerSync.LOGGER.warn("Chat server connection timeout, reconnecting..."); PlayerSync.LOGGER.warn("Chat server read timeout, reconnecting...");
} catch (ConnectException e) {
PlayerSync.LOGGER.warn("Cannot connect to chat server: {}", e.getMessage());
} catch (IOException e) { } catch (IOException e) {
PlayerSync.LOGGER.error("Chat client connection error: {}", e.getMessage()); PlayerSync.LOGGER.error("Chat client connection error: {}", e.getMessage());
} finally { } finally {
@ -82,17 +95,40 @@ public class ChatSyncClient {
reconnectAttempts, MAX_RECONNECT_ATTEMPTS); reconnectAttempts, MAX_RECONNECT_ATTEMPTS);
try { try {
Thread.sleep(RECONNECT_DELAY); long delay = Math.min(RECONNECT_DELAY * (long)Math.pow(2, reconnectAttempts-1), 60000);
Thread.sleep(delay);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
break; break;
} }
} }
} }
}
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { private void startHeartbeatMonitor() {
PlayerSync.LOGGER.error("Failed to connect to chat server after {} attempts", MAX_RECONNECT_ATTEMPTS); Thread heartbeatThread = new Thread(() -> {
} while (running && clientSocket != null && !clientSocket.isClosed()) {
try {
Thread.sleep(10000); // 每10秒检查一次
long now = System.currentTimeMillis();
if (now - lastHeartbeat > HEARTBEAT_INTERVAL) {
PlayerSync.LOGGER.warn("No heartbeat for {}ms, sending test message",
now - lastHeartbeat);
// 发送测试消息检查连接
if (out != null) {
out.println("<heartbeat>");
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "ChatSync-Heartbeat");
heartbeatThread.setDaemon(true);
heartbeatThread.start();
} }
private void closeConnection() { private void closeConnection() {

View File

@ -3,10 +3,7 @@ package vip.fubuki.playersync.sync.chat;
import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.PlayerSync;
import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.config.JdbcConfig;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@ -29,6 +26,8 @@ public class ChatSyncServer {
serverSocket.setReuseAddress(true); serverSocket.setReuseAddress(true);
PlayerSync.LOGGER.info("Chat server started successfully on port {}", JdbcConfig.CHAT_SERVER_PORT.get()); PlayerSync.LOGGER.info("Chat server started successfully on port {}", JdbcConfig.CHAT_SERVER_PORT.get());
startHeartbeatBroadcast();
while (running && !Thread.currentThread().isInterrupted()) { while (running && !Thread.currentThread().isInterrupted()) {
try { try {
Socket newSocket = serverSocket.accept(); Socket newSocket = serverSocket.accept();
@ -97,6 +96,47 @@ public class ChatSyncServer {
} }
} }
private void startHeartbeatBroadcast() {
Thread heartbeatThread = new Thread(() -> {
while (running) {
try {
Thread.sleep(20000);
broadcastHeartbeat();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "ChatSync-Server-Heartbeat");
heartbeatThread.setDaemon(true);
heartbeatThread.start();
}
private void broadcastHeartbeat() {
Iterator<Socket> iterator = SocketList.iterator();
while (iterator.hasNext()) {
Socket socket = iterator.next();
if (!socket.isClosed()) {
try {
PrintWriter writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
writer.println("<heartbeat>");
} catch (IOException e) {
PlayerSync.LOGGER.warn("Failed to send heartbeat to client, removing: {}", e.getMessage());
iterator.remove();
try {
socket.close();
} catch (IOException ex) {
// Ignore
}
}
} else {
iterator.remove();
}
}
}
public void shutdown() { public void shutdown() {
running = false; running = false;
try { try {