From a5041917a4733cbbd70f8369cb70ca163a25c9d4 Mon Sep 17 00:00:00 2001 From: mlus <1319237806@qq.com> Date: Tue, 14 Oct 2025 13:09:43 +0800 Subject: [PATCH] chat sync reconnect system --- gradle.properties | 2 +- .../vip/fubuki/playersync/sync/ChatSync.java | 25 +++- .../playersync/sync/chat/ChatSyncClient.java | 103 +++++++++++++--- .../playersync/sync/chat/ChatSyncServer.java | 112 ++++++++++++++---- 4 files changed, 197 insertions(+), 45 deletions(-) diff --git a/gradle.properties b/gradle.properties index d17616c..425c1f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,7 +34,7 @@ mod_name=PlayerSync # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPL-3.0 license # The mod version. See https://semver.org/ -mod_version=2.1.3 +mod_version=2.1.4 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/vip/fubuki/playersync/sync/ChatSync.java b/src/main/java/vip/fubuki/playersync/sync/ChatSync.java index 0414637..ed77fe7 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ChatSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/ChatSync.java @@ -11,28 +11,45 @@ import java.io.IOException; public class ChatSync { public static final Logger LOGGER = LogUtils.getLogger(); + private static ChatSyncServer chatSyncServer; + private static ChatSyncClient chatSyncClient; public static void register(){ if(JdbcConfig.IS_CHAT_SERVER.get()) { LOGGER.info("Trying to setup chat server at port " + JdbcConfig.CHAT_SERVER_PORT.get()); new Thread(()->{ - ChatSyncServer chatSyncServer = new ChatSyncServer(); + chatSyncServer = new ChatSyncServer(); try { chatSyncServer.run(); } catch (IOException e) { LOGGER.error("Unable to start chat server", e); } - }).start(); + }, "ChatSync-Server").start(); } new Thread(()->{ + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + LOGGER.info("Trying to connect to chat server " + JdbcConfig.CHAT_SERVER_IP.get() + ":" + JdbcConfig.CHAT_SERVER_PORT.get()); - ChatSyncClient chatSyncClient = new ChatSyncClient(); + chatSyncClient = new ChatSyncClient(); chatSyncClient.run(); - }).start(); + }, "ChatSync-Client").start(); MinecraftForge.EVENT_BUS.register(ChatSyncClient.class); } + + public static void shutdown() { + if (chatSyncServer != null) { + chatSyncServer.shutdown(); + } + if (chatSyncClient != null) { + chatSyncClient.shutdown(); + } + } } 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 33379b3..1b65b32 100644 --- a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncClient.java @@ -6,13 +6,14 @@ import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.config.JdbcConfig; -import vip.fubuki.playersync.sync.ChatSync; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; +import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketTimeoutException; import java.util.Objects; public class ChatSyncClient { @@ -20,31 +21,97 @@ public class ChatSyncClient { static Socket clientSocket; static PrintWriter out; + private static volatile boolean running = true; + private static final int RECONNECT_DELAY = 5000; + private static final int MAX_RECONNECT_ATTEMPTS = 10; + public void run() { - try { - clientSocket = new Socket(JdbcConfig.CHAT_SERVER_IP.get(), JdbcConfig.CHAT_SERVER_PORT.get()); - out = new PrintWriter(clientSocket.getOutputStream(),true); + int reconnectAttempts = 0; - BufferedReader in = new BufferedReader( - new InputStreamReader(clientSocket.getInputStream())); + while (running && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + try { + PlayerSync.LOGGER.info("Connecting to chat server {}:{}", + JdbcConfig.CHAT_SERVER_IP.get(), + JdbcConfig.CHAT_SERVER_PORT.get()); - String serverMessage; - while ((serverMessage = in.readLine()) != null) { - PlayerSync.LOGGER.info("Received message from chat server: " + serverMessage); - Component textComponents = Component.nullToEmpty(serverMessage); - if(playerList != null){ - playerList.broadcastSystemMessage(textComponents,false); + clientSocket = new Socket(); + + clientSocket.connect( + new InetSocketAddress( + JdbcConfig.CHAT_SERVER_IP.get(), + JdbcConfig.CHAT_SERVER_PORT.get() + ), + 10000 + ); + + clientSocket.setSoTimeout(30000); + + out = new PrintWriter(clientSocket.getOutputStream(), true); + BufferedReader in = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream())); + + PlayerSync.LOGGER.info("Successfully connected to chat server"); + reconnectAttempts = 0; + + String serverMessage; + while (running && (serverMessage = in.readLine()) != null) { + 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)); + } + } + } + + } catch (SocketTimeoutException e) { + PlayerSync.LOGGER.warn("Chat server connection timeout, reconnecting..."); + } catch (IOException e) { + PlayerSync.LOGGER.error("Chat client connection error: {}", e.getMessage()); + } finally { + closeConnection(); + } + + if (running && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + reconnectAttempts++; + PlayerSync.LOGGER.warn("Attempting to reconnect to chat server ({}/{})", + reconnectAttempts, MAX_RECONNECT_ATTEMPTS); + + try { + Thread.sleep(RECONNECT_DELAY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; } } - } catch (IOException e) { - e.printStackTrace(); - reconnectClient(); + } + + if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { + PlayerSync.LOGGER.error("Failed to connect to chat server after {} attempts", MAX_RECONNECT_ATTEMPTS); } } - private void reconnectClient() { - ChatSync.LOGGER.warn("TODO: implement reconnectClient()"); - //TODO + private void closeConnection() { + try { + if (out != null) { + out.close(); + out = null; + } + if (clientSocket != null && !clientSocket.isClosed()) { + clientSocket.close(); + clientSocket = null; + } + } catch (IOException e) { + PlayerSync.LOGGER.error("Error closing connection: {}", e.getMessage()); + } + } + + public void shutdown() { + running = false; + closeConnection(); } @SubscribeEvent 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 a9ecf65..1f9fabe 100644 --- a/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java +++ b/src/main/java/vip/fubuki/playersync/sync/chat/ChatSyncServer.java @@ -1,63 +1,131 @@ 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.InputStream; -import java.io.OutputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.Iterator; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; public class ChatSyncServer { static ServerSocket serverSocket; static final Set SocketList = ConcurrentHashMap.newKeySet(); static final ExecutorService executorService = Executors.newCachedThreadPool(); + private volatile boolean running = true; public void run() throws IOException { - serverSocket = new ServerSocket(JdbcConfig.CHAT_SERVER_PORT.get()); - while (!Thread.currentThread().isInterrupted()) { - Socket newSocket = serverSocket.accept(); - SocketList.add(newSocket); - executorService.submit(() -> handleClient(newSocket)); + try { + serverSocket = new ServerSocket(JdbcConfig.CHAT_SERVER_PORT.get()); + serverSocket.setReuseAddress(true); + PlayerSync.LOGGER.info("Chat server started successfully on port {}", JdbcConfig.CHAT_SERVER_PORT.get()); + + while (running && !Thread.currentThread().isInterrupted()) { + try { + Socket newSocket = serverSocket.accept(); + newSocket.setSoTimeout(30000); + SocketList.add(newSocket); + executorService.submit(() -> handleClient(newSocket)); + PlayerSync.LOGGER.info("New client connected, total clients: {}", SocketList.size()); + } catch (IOException e) { + if (running) { + PlayerSync.LOGGER.error("Error accepting client connection: {}", e.getMessage()); + } + } + } + } finally { + shutdown(); } - serverSocket.close(); } private void handleClient(Socket socket) { - try (InputStream inputStream = socket.getInputStream()) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - String message = new String(buffer, 0, bytesRead); + String clientInfo = socket.getInetAddress() + ":" + socket.getPort(); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream()))) { + + String message; + while (running && (message = reader.readLine()) != null) { + PlayerSync.LOGGER.info("Received message from {}: {}", clientInfo, message); broadcastMessage(socket, message); } + + } catch (SocketTimeoutException e) { + PlayerSync.LOGGER.warn("Client {} timeout", clientInfo); } catch (IOException e) { - e.printStackTrace(); + PlayerSync.LOGGER.error("Error handling client {}: {}", clientInfo, e.getMessage()); } finally { SocketList.remove(socket); try { - socket.close(); + if (!socket.isClosed()) { + socket.close(); + } } catch (IOException e) { - e.printStackTrace(); + PlayerSync.LOGGER.error("Error closing client socket: {}", e.getMessage()); } + PlayerSync.LOGGER.info("Client disconnected, remaining clients: {}", SocketList.size()); } } private void broadcastMessage(Socket sender, String message) { - for (Socket socket : SocketList) { - if (!socket.equals(sender)) { + Iterator iterator = SocketList.iterator(); + while (iterator.hasNext()) { + Socket socket = iterator.next(); + if (!socket.equals(sender) && !socket.isClosed()) { try { - OutputStream outputStream = socket.getOutputStream(); - outputStream.write(message.getBytes()); - outputStream.flush(); + PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); + writer.println(message); } catch (IOException e) { - e.printStackTrace(); + PlayerSync.LOGGER.error("Error broadcasting to client, removing: {}", e.getMessage()); + iterator.remove(); + try { + socket.close(); + } catch (IOException ex) { + // Ignore + } } } } } + + public void shutdown() { + running = false; + try { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + } catch (IOException e) { + PlayerSync.LOGGER.error("Error closing server socket: {}", e.getMessage()); + } + + for (Socket socket : SocketList) { + try { + if (!socket.isClosed()) { + socket.close(); + } + } catch (IOException e) { + // Ignore + } + } + SocketList.clear(); + + executorService.shutdown(); + try { + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + executorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } }