重启 jar 文件后出现异常会导致套接字错误。

huangapple go评论72阅读模式
英文:

Restarting jar after exception causes socket errors

问题

所以我有一个在IntelliJ IDE中运行正常的多线程服务器
项目是一个gradle项目每当一个新客户端连接到服务器时以下内容会在一个新线程类ClientHandler中启动

@Override
public void run() {
    planetNames.addAll(Arrays.asList(names));
    while (clientRunning) {
        // ==================== 登录 ====================
        try {
            this.username = receive.readLine().replace("[LOGIN]:", "");
            if (serverServiceCommunicator.isLoggedIn(username)) {
                send.println(false);
            } else {
                send.println(true);
                // ==================== 新游戏 ====================
                try {
                    this.user = serverServiceCommunicator.getUserService().getUser(username);
                    if (user.isFirstGame()) {
                        send.println("[NEW-GAME]");
                        // ==================== 世界生成 ====================
                        int difficulty = Integer.parseInt(receive.readLine());
                        this.seed = UUID.randomUUID().hashCode();
                        Overworld overworld = generateOverworld(this.seed, username, difficulty);
                        overworldDAO.persist(overworld);
                        user.setOverworld(overworld);
                        //====================== 飞船生成 ==================
                        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);
                    }
                    // ==================== 更新登录状态 ====================
                    user.setLoggedIn(true);
                    serverServiceCommunicator.getUserService().updateUser(user);
                    // ==================== 获取飞船 ====================
                    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());
                    }
                    // ==================== 获取地图 ====================
                    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;
                // ===== 添加到已连接客户端 =====
                this.serverServiceCommunicator.getPvpClients().add(username);
                // ==================== 运行中 ====================
                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();
            // 由于异常,套接字将被关闭,因此无法发送更多数据
            // 线程将因套接字异常而终止
            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();
    }
}
// Server 运行方法
public void run(){
    synchronized (this){
        this.serverThread = Thread.currentThread();
    }
    bindPort(this.port);
    System.out.println("服务器初始化在 " + serverSocket.getInetAddress().getHostAddress() + ":" + this.port + ",等待连接...");
    while (isRunning()){
        Socket clientSocket = null;
        try {
            clientSocket = serverSocket.accept();
            clientSocket.setSoTimeout(0);
            System.out.println("接受来自 " + 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 {
        // ==================== 登录 ====================
        send.println("[LOGIN]:" + username);
        String received = receive.readLine();
        // ==================== 异常 ====================
        if (received.contains("[EXCEPTION]:[LOGIN]")){
            System.out.println("<CLIENT>:[登录期间出现异常!终止...]");
            throw new IllegalArgumentException();
        }
        // ==================== 成功登录 ====================
        else if (received.equals("true")){
            System.out.println("<CLIENT>:[登录成功]:[用户名]:" + username);
            received = receive.readLine();
            // ==================== 新游戏 ====================
            if (received.equals("[NEW-GAME]")){
                System.out.println("<CLIENT>:[新游戏]:[用户名]:"+username+":[飞船类型]:"+shipType+":[难度]:"+difficulty);
                send.println(difficulty);
                sendObject.writeObject(shipType);
                received = receive.readLine();
            }
            // ==================== 获取飞船 ====================
            if (received.equals("[FETCH-SHIP]")){
                System.out.println("<CLIENT>:[获取飞船]:[用户名]:"+username);
                try {
                    this.myShip = (Ship) receiveObject.readObject();
                    System.out.println("<CLIENT>:[接收到飞船]:[用户名]:"+username+":[飞船ID]:"+myShip

<details>
<summary>英文:</summary>

So I have a multithreaded server that works just fine in the IntelliJ IDE. 
The project is a gradle project. Whenever a new client connects to the server, the following is started in a new thread (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 Constructor:

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();
}
}


Server run (Class Server):

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();
}
}


Client side: (Login only requires username, as it is a local multiplayer game)

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();
}
}


Client Constructor:

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]");
}
}


As seen above I have tried adding a dozen flush() calls to prevent my problem.
The game runs just fine the first time. But if during a login stage there is an exception, due to which the game crashes, upon restarting the jar I get all kinds of exceptions, from OptionalDataExceptions to StreamCorruptedExceptions with different typecodes (Typecode 00 for instance).
The weird thing is, that this only happens with the generated Jar file. In the IDE I can rerun the application dozens of time without errors, even after the exception is thrown. To be able to run the jar again, I need to delete the database file, clear my temp folder and force exit the Jar through task manager.
I believe this is due to data that has been sent but has not been received by the client, so I looked into ways of flushing all socket data before using it, however I have not found anything that helps.
The game is created using LibGDX, the way of storing the data is using Hibernate with an H2 Database (this is obviously bad because of performance but is a requirement).
Any help is much appreciated :]
</details>
# 答案1
**得分**: 0
Turns out having 2 objects written and read from the other end caused the object streams to get corrupted. Fixed it by adding a send/receive before sending the other object. (But why though? Its a TCP socket not a UDP one)
<details>
<summary>英文:</summary>
Turns out having 2 objects written and read from the other end caused the object streams to get corrupted. Fixed it by adding a send/receive before sending the other object. (But why though? Its a TCP socket not a UDP one)
</details>

huangapple
  • 本文由 发表于 2020年7月27日 21:59:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/63116944.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定