异常导致套接字错误后重新启动 jar

Restarting jar after exception causes socket errors

所以我有一个多线程服务器,它在 IntelliJ IDE 中工作得很好。 该项目是一个 gradle 项目。每当新客户端连接到服务器时,都会在新线程 (Class ClientHandler) 中启动以下内容:

 @Override
    public void run() {
        planetNames.addAll(Arrays.asList(names));
        while(clientRunning) {
            // ==================== LOGIN ====================
            try {
                this.username = receive.readLine().replace("[LOGIN]:", "");
                if (serverServiceCommunicator.isLoggedIn(username)) {
                    send.println(false);
                } else {
                    send.println(true);
                    // ==================== NEW GAME ====================
                    try {
                        this.user = serverServiceCommunicator.getUserService().getUser(username);
                        if (user.isFirstGame()) {
                            send.println("[NEW-GAME]");
                            // ==================== Overworld Creation ====================
                            int difficulty = Integer.parseInt(receive.readLine());
                            this.seed = UUID.randomUUID().hashCode();
                            Overworld overworld = generateOverworld(this.seed, username, difficulty);
                            overworldDAO.persist(overworld);
                            user.setOverworld(overworld);
                            //====================== Ship Creation ==================
                            ShipType shipType = (ShipType) receiveObject.readObject();
                            Ship ship = generateShip(shipType, username, overworld.getStartPlanet());
                            for (Room r : ship.getSystems()){
                                if (r.isSystem() && ((System) r).getEnergy()==0){
                                    ((System) r).setDisabled(true);
                                }
                            }
                            shipDAO.persist(ship);
                            user.setUserShip(ship);
                            Ship userShip = user.getUserShip();
                            Planet startPlanet = overworld.getStartPlanet();
                            List<Ship> startPlanetShips = startPlanet.getShips();
                            startPlanetShips.add(userShip);
                            startPlanet.setShips(startPlanetShips);
                            userShip.setPlanet(startPlanet);
                            //=======================================================
                            user.setFirstGame(false);
                        }
                        // ==================== UPDATE LOGIN ====================
                        user.setLoggedIn(true);
                        serverServiceCommunicator.getUserService().updateUser(user);
                        // ==================== FETCH SHIP ====================
                        try {
                            send.println("[FETCH-SHIP]");
                            sendObject.writeObject(this.serverServiceCommunicator.getClientShip(username));
                        } catch (Exception f) {
                            f.printStackTrace();
                            send.println("[EXCEPTION]:[FETCH-SHIP]:[USERNAME]:" + username);
                            Server.getInstance().killServer();
                            throw new IllegalArgumentException(f.getMessage());
                        }
                        // ==================== FETCH MAP ====================
                        try {
                            send.println("[FETCH-MAP]");
                            sendObject.writeObject(this.serverServiceCommunicator.getClientMap(username));
                        } catch (Exception f) {
                            f.printStackTrace();
                            send.println("[EXCEPTION]:[FETCH-MAP]:[USERNAME]:" + username);
                            Server.getInstance().killServer();
                            throw new IllegalArgumentException(f.getMessage());
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        send.println("[EXCEPTION]:[NEW-GAME]:[USERNAME]:" + username);
                    }
                    gameActive = true;
                    // ===== Add to connected clients =====
                    this.serverServiceCommunicator.getPvpClients().add(username);
                    // ==================== RUNNING ====================
                    while (gameActive) {
                        if (!clientSocket.getInetAddress().isReachable(2000)) {
                            RequestObject requestObject = new RequestObject();
                            requestObject.setRequestType(RequestType.LOGOUT);
                            requestObject.setUsername(username);
                            this.serverServiceCommunicator.getResponse(requestObject);
                            java.lang.System.out.println("[Client-Disconnected]:[Auto-Logout]");
                        } else {
                            RequestObject request = (RequestObject) receiveObject.readObject();
                            sendObject.flush();
                            sendObject.writeObject(this.serverServiceCommunicator.getResponse(request));
                            if (request.getRequestType() == RequestType.LOGOUT) {
                                gameActive = false;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                // Socket will be closed thanks to exception, therefor cannot send more data
                // Thread will terminate with socket exception
                try {
                    serverServiceCommunicator.logoutAfterException(username);
                    clientSocket.close();
                    Server.getInstance().killServer();
                } catch (Exception f) {
                    f.printStackTrace();
                }
            }
        }
    }

ClientHandler 构造函数:

public ClientHandler(Socket clientSocket, Server server) throws IllegalArgumentException {
        this.clientSocket = clientSocket;
        this.server = server;
        try {
            sendObject = new ObjectOutputStream(clientSocket.getOutputStream());
            send = new PrintWriter(clientSocket.getOutputStream(), true);
            receiveObject = new ObjectInputStream(clientSocket.getInputStream());
            receive = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            serverServiceCommunicator = ServerServiceCommunicator.getInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException();
        }
    }

服务器运行(Class服务器):

public void run(){
        synchronized (this){
            this.serverThread = Thread.currentThread();
        }
        bindPort(this.port);
        System.out.println("Server initialized on " + serverSocket.getInetAddress().getHostAddress() + ":" + this.port + ", listening for connections...");
        while (isRunning()){
            Socket clientSocket = null;
            try {
                clientSocket = serverSocket.accept();
                clientSocket.setSoTimeout(0);
                System.out.println("Accepted new connection from "+ clientSocket.getInetAddress().getHostAddress());
            }
            catch (Exception e){
                e.printStackTrace();
            }
            Server server = this;
            new Thread(
                  new ClientHandler(clientSocket,server)
            ).start();
        }
    }

客户端:(登录只需要用户名,因为是本地多人游戏)

public boolean login(String username, ShipType shipType, int difficulty) throws IllegalArgumentException {
        try {
            // ==================== LOG-IN ====================
            send.println("[LOGIN]:" + username);
            String received = receive.readLine();
            // ==================== EXCEPTION ====================
            if (received.contains("[EXCEPTION]:[LOGIN]")){
                System.out.println("<CLIENT>:[EXCEPTION DURING LOGIN! TERMINATING...]");
                throw new IllegalArgumentException();
            }
            // ==================== SUCCESSFUL LOGIN ====================
            else if (received.equals("true")){
                System.out.println("<CLIENT>:[LOGIN SUCCESSFUL]:[USERNAME]:" + username);
                received = receive.readLine();
                // ==================== NEW GAME ====================
                if (received.equals("[NEW-GAME]")){
                    System.out.println("<CLIENT>:[NEW-GAME]:[USERNAME]:"+username+":[SHIP-TYPE]:"+shipType+":[DIFFICULTY]:"+difficulty);
                    send.println(difficulty);
                    sendObject.writeObject(shipType);
                    received = receive.readLine();
                }
                // ==================== FETCH SHIP ====================
                if (received.equals("[FETCH-SHIP]")){
                    System.out.println("<CLIENT>:[FETCH-SHIP]:[USERNAME]:"+username);
                    try {
                        this.myShip = (Ship) receiveObject.readObject();
                        System.out.println("<CLIENT>:[RECEIVED-SHIP]:[USERNAME]:"+username+":[SHIP-ID]:"+myShip.getId());
                    }
                    catch (Exception f){
                        f.printStackTrace();
                        System.out.println("<CLIENT>:[EXCEPTION]:[FETCH-SHIP]:[USERNAME]:"+username);
                        throw new IllegalArgumentException();
                    }
                    received = receive.readLine();
                }
                // ==================== FETCH MAP ====================
                if (received.equals("[FETCH-MAP]")){
                    try {
                        this.overworld = (Overworld) receiveObject.readObject();
                        System.out.println("<CLIENT>:[RECEIVED-MAP]:[USERNAME]:"+username+":[MAP-ID]:"+overworld.getId());
                    }
                    catch (Exception f){
                        f.printStackTrace();
                        System.out.println("<CLIENT>:[EXCEPTION]:[FETCH-MAP]:[USERNAME]:"+username);
                        throw new IllegalArgumentException();
                    }
                }
                return true;
            }
            // ==================== FAILED LOGIN ====================
            else {
                return false;
            }
        }
        catch (Exception e){
            e.printStackTrace();
            try {
                socket.close();
            }
            catch (Exception f){
                f.printStackTrace();
            }
            throw new IllegalArgumentException();
        }
    }

客户端构造函数:

public Client(@NonNull String ipAddress, @NonNull int port) throws IllegalArgumentException {
        try {
            socket = new Socket();
            socket.connect(new InetSocketAddress(ipAddress,port),0);
            send = new PrintWriter(socket.getOutputStream(), true);
            sendObject = new ObjectOutputStream(socket.getOutputStream());
            receive = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            receiveObject = new ObjectInputStream(socket.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException("<CLIENT>:[Couldn't initialize connection to server]");
        }
    }

如上所示,我已经尝试添加十几个 flush() 调用来防止我的问题。

第一次玩游戏运行还不错。但是,如果在登录阶段出现异常,导致游戏崩溃,则在重新启动 jar 时,我会收到各种异常,从 OptionalDataExceptions 到具有不同类型代码(例如类型代码 00)的 StreamCorruptedExceptions。

奇怪的是,这只发生在生成的 Jar 文件中。在 IDE 中,我可以重新 运行 应用程序数十次而不会出现错误,即使在抛出异常之后也是如此。为了能够再次 运行 jar,我需要删除数据库文件,清除我的临时文件夹并通过任务管理器强制退出 Jar。

我认为这是由于数据已发送但客户端尚未收到,因此我研究了在使用之前刷新所有套接字数据的方法,但是我没有找到任何有用的方法。

游戏是使用 LibGDX 创建的,存储数据的方式是使用 Hibernate 和 H2 数据库(这显然很糟糕,因为性能但这是一项要求)。

非常感谢任何帮助:]

事实证明,从另一端写入和读取 2 个对象会导致对象流损坏。通过在发送另一个对象之前添加 send/receive 来修复它。 (但为什么呢?它是 TCP 套接字而不是 UDP 套接字)