Issue
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 :]
Solution
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)
Answered By - leolion3