diff --git a/src/main/java/vip/fubuki/playersync/CommandInit.java b/src/main/java/vip/fubuki/playersync/CommandInit.java index e10de3d..c20866b 100644 --- a/src/main/java/vip/fubuki/playersync/CommandInit.java +++ b/src/main/java/vip/fubuki/playersync/CommandInit.java @@ -6,7 +6,6 @@ import net.minecraft.commands.Commands; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.RegisterCommandsEvent; -import vip.fubuki.playersync.sync.chat.ChatSyncClient; @EventBusSubscriber() public class CommandInit { @@ -18,7 +17,6 @@ public class CommandInit { .requires(cs->cs.hasPermission(2)) .then(Commands.literal("reconnect") .executes(context -> { - new ChatSyncClient().run(); // context.getSource().sendSuccess(()->MutableComponent.create(new TranslatableContents("playersync.command.reconnect")),true); return 0; } diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index 8be22a0..de37b07 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -10,6 +10,7 @@ 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 vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.sync.ChatSync; @@ -179,4 +180,9 @@ public class PlayerSync { LOGGER.info("PlayerSync is ready!"); } + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event){ + ChatSync.shutdown(); + } + } diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index c8530e6..41747bc 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -85,7 +85,7 @@ public class VanillaSync { File gameDir = Objects.requireNonNull(serverPlayer.getServer()).getServerDirectory().toFile(); final MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); - if (server != null && server.isDedicatedServer()) { + if (server.isDedicatedServer()) { PlayerSync.LOGGER.debug("Attempting to write dedicated server advancement file"); File advancements = new File(gameDir, getSyncWorldForServer() + "/advancements" + "/" + player_uuid + ".json"); diff --git a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java index 170fecb..b207915 100644 --- a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java @@ -8,10 +8,8 @@ import net.neoforged.neoforge.event.entity.player.PlayerEvent; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.config.JdbcConfig; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; +import java.io.*; +import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; @@ -26,6 +24,9 @@ public class ChatSyncClient { private static final int RECONNECT_DELAY = 5000; 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() { int reconnectAttempts = 0; @@ -36,40 +37,52 @@ public class ChatSyncClient { JdbcConfig.CHAT_SERVER_PORT.get()); clientSocket = new Socket(); + clientSocket.setReuseAddress(true); + clientSocket.setKeepAlive(true); + clientSocket.setTcpNoDelay(true); clientSocket.connect( new InetSocketAddress( JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_PORT.get() ), - 10000 + 15000 ); clientSocket.setSoTimeout(30000); - out = new PrintWriter(clientSocket.getOutputStream(), true); - BufferedReader in = new BufferedReader( - new InputStreamReader(clientSocket.getInputStream())); + out = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(clientSocket.getOutputStream())), true); PlayerSync.LOGGER.info("Successfully connected to chat server"); reconnectAttempts = 0; + lastHeartbeat = System.currentTimeMillis(); + + startHeartbeatMonitor(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream())); String serverMessage; while (running && (serverMessage = in.readLine()) != null) { + lastHeartbeat = System.currentTimeMillis(); + + if ("".equals(serverMessage)) { + continue; + } + PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage); Component textComponents = Component.nullToEmpty(serverMessage); if(playerList != null){ - if (playerList.getServer().isSameThread()) { - playerList.broadcastSystemMessage(textComponents, false); - } else { - playerList.getServer().execute(() -> - playerList.broadcastSystemMessage(textComponents, false)); - } + playerList.getServer().execute(() -> + playerList.broadcastSystemMessage(textComponents, false)); } } } 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) { PlayerSync.LOGGER.error("Chat client connection error: {}", e.getMessage()); } finally { @@ -82,17 +95,40 @@ public class ChatSyncClient { reconnectAttempts, MAX_RECONNECT_ATTEMPTS); try { - Thread.sleep(RECONNECT_DELAY); + long delay = Math.min(RECONNECT_DELAY * (long)Math.pow(2, reconnectAttempts-1), 60000); + Thread.sleep(delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } + } - if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { - PlayerSync.LOGGER.error("Failed to connect to chat server after {} attempts", MAX_RECONNECT_ATTEMPTS); - } + private void startHeartbeatMonitor() { + 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(""); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + }, "ChatSync-Heartbeat"); + heartbeatThread.setDaemon(true); + heartbeatThread.start(); } private void closeConnection() { diff --git a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java index 1f9fabe..6697d6b 100644 --- a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java @@ -3,10 +3,7 @@ package vip.fubuki.playersync.sync.chat; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.config.JdbcConfig; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; +import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; @@ -29,6 +26,8 @@ public class ChatSyncServer { serverSocket.setReuseAddress(true); PlayerSync.LOGGER.info("Chat server started successfully on port {}", JdbcConfig.CHAT_SERVER_PORT.get()); + startHeartbeatBroadcast(); + while (running && !Thread.currentThread().isInterrupted()) { try { 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 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(""); + } 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() { running = false; try {