diff --git a/src/main/java/vip/fubuki/playersync/CommandInit.java b/src/main/java/vip/fubuki/playersync/CommandInit.java index 358513a..06fc216 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.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import vip.fubuki.playersync.sync.chat.ChatSyncClient; @Mod.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 dedd0f8..7d8c257 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -5,6 +5,7 @@ import com.mysql.cj.jdbc.Driver; import net.minecraft.SharedConstants; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.ModList; @@ -193,6 +194,11 @@ public class PlayerSync { LOGGER.info("PlayerSync is ready!"); } + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event){ + ChatSync.shutdown(); + } + private static void addColumnIfNotExists(String tableName, String columnName, String dataTypeDefaultNullness, boolean makePrimaryKey) throws SQLException { 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 1b65b32..8915bf9 100644 --- a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java @@ -7,10 +7,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; 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; @@ -25,6 +23,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; @@ -35,40 +36,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 { @@ -81,17 +94,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 {