异常导致套接字错误后重新启动 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 套接字)
所以我有一个多线程服务器,它在 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 套接字)