1

I'm using a Digital Ocean server to facilitate online multiplayer in my PC game. The server is a small application written in Java. The problem is that the server keeps going down occasionally and restarts itself automatically after about 15 minutes. I have upgraded the server a couple of times but it didn't seem to make any difference, finally I'm using a CPU Optimized Droplet with 4 GB memory 2 vCPUs and 20 GB disk. I'm not completely sure but it seemed that the server went down when there were about 15 people online, but at times there are the same amount of people online (or more) and no crash occurs. It crashes about 1-2 times a day when most people are online.

Graphs:

-1 minute after server crashed (7:29 pm) : https://imgur.com/a/Asit3O6

-Server down for 12 minutes (7:41 pm): https://imgur.com/a/ulIgTau

-Server back online (7:50 pm): https://imgur.com/a/gMOn4Jf

-Server back online for 28 minutes (8:18 pm): https://imgur.com/a/lA8dufN

Console:

-After server crashed (7:39 Pm): https://imgur.com/a/h7r701H it shows:

java.net.SocketException: Connection reset

-After the server is back online (7:50 pm): https://imgur.com/a/l85FYFw

The only thing I recognize is the ClientThread.java file which is one of the 3 java files that comprises the server. I must also say that my experience with Java is very basic and that someone else made the java server files (who is not available), but I understand most part of it and made some small adjustments myself.

Any reason why this server keeps crashing occasionally?

Here is the ClientThread.java file:

import java.util.Iterator;
import java.io.IOException;
import java.io.Reader;
import java.io.InputStreamReader;
import java.util.regex.Pattern;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.net.Socket;

// 
// Decompiled by Procyon v0.5.30
// 

public class ClientThread extends Thread
{
    Socket clientSocket_;
    String clientIp_;
    String serverIp_;
    ServerInformation server_;
    PrintWriter out_;
    BufferedReader in_;
    boolean prepareTermination_;
    boolean terminated_;
    private static final Pattern numberPattern;

    static {
        numberPattern = Pattern.compile("\\d+");
    }

    public ClientThread(final Socket sock) {
        this.clientSocket_ = sock;
        this.clientIp_ = this.clientSocket_.getRemoteSocketAddress().toString();
        this.serverIp_ = null;
        this.server_ = null;
        this.prepareTermination_ = false;
        this.terminated_ = false;
    }

    @Override
    public void run() {
        try {
            //this.logDebugMessage(2, "Client socket accepted, ip: " + this.clientIp_);
            this.out_ = new PrintWriter(this.clientSocket_.getOutputStream(), true);
            this.in_ = new BufferedReader(new InputStreamReader(this.clientSocket_.getInputStream()));
            long lastActionTime = System.currentTimeMillis();
            while (true) {
                if (this.in_.ready() || System.currentTimeMillis() - lastActionTime >= 1000 * Main.threadTimeout_) {
                    if (System.currentTimeMillis() - lastActionTime >= 1000 * Main.threadTimeout_) {
                        //this.logDebugMessage(3, "Thread was killed due to prolonged inactivity (" + Main.threadTimeout_ + " seconds)");
                        this.terminateThread();
                        return;
                    }
                    lastActionTime = System.currentTimeMillis();
                    final String inputLine = this.in_.readLine().trim();
                    if (ClientThread.numberPattern.matcher(inputLine).matches()) {
                        final int val = Integer.parseInt(inputLine);
                        switch (val) {
                            case 111: { //send to client fast b
                                final StringBuilder msg = new StringBuilder();
                                    msg.append(String.valueOf(this.in_.readLine().trim()));
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                    if (thread2 != this) {
                                        thread2.out_.print(msg);
                                        thread2.out_.flush();
                                    }
                                }
                                break;
                            }                           
                            case 110: { //send to ip fast
                                final String ip = this.in_.readLine().trim();
                                final ClientThread target = this.server_.ipToClientThread_.getOrDefault(ip, null);
                                if (target != null) {
                                    target.out_.print(this.in_.readLine().trim());
                                    target.out_.flush();
                                    break;
                                }                               
                            }                               
                            case 109: { //send to server fast
                                this.server_.out_.print(this.in_.readLine().trim());
                                this.server_.out_.flush();
                                break;
                            }                               
                            case 108: { //send to all fast
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {          
                                        thread2.out_.print(this.in_.readLine().trim());
                                        thread2.out_.flush();
                                }
                                break;
                            }                           
                            case 107: { //send to others fast
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                    if (thread2 != this) {
                                        thread2.out_.print(this.in_.readLine().trim());
                                        thread2.out_.flush();
                                    }
                                }
                                break;
                            }                           
                            case 106: {
                                this.server_.description_ = this.in_.readLine().trim(); //class                             
                                break;
                            }                                                           
                            case 105: {
                                this.out_.print("*" + 26 + "|" + this.server_.servervar3_ + "|" + this.server_.servervar4_ + "|" + this.server_.servervar5_ + "~" + "|");
                                this.out_.flush();
                                break;
                            }                               
                            case 104: {
                                this.server_.servervar5_ = this.in_.readLine().trim(); //status         
                                break;
                            }                                   
                            case 103: {
                                this.server_.servervar3_ = this.in_.readLine().trim(); //current lap                
                                break;
                            }                                       
                            case 102: {
                                this.server_.servervar3_ = this.in_.readLine().trim(); //current lap
                                this.server_.servervar4_ = this.in_.readLine().trim(); //total laps
                                this.server_.servervar5_ = this.in_.readLine().trim(); //status                     
                                break;
                            }                       
                            case 101: { //admin quit server
                                final String ipServer = this.in_.readLine().trim();
                                final ServerInformation info = Main.servers_.getOrDefault(ipServer, null);
                                this.server_ = info;                
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                        thread2.out_.print("*" + 22 + "|" + -1 + "~" + "|");
                                        thread2.out_.flush();
                                }                                   
                                //this.logDebugMessage(1, "A game server has been deleted, ip: " + ipServer);
                                Main.servers_.remove(ipServer);                                                     
                                break;
                            }                           
                            case 100: { 
                                if (System.currentTimeMillis() - this.server_.servervar2_ >= 1000 * 20) { //clients check if server is still online
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                        thread2.out_.print("*" + 22 + "|" + -1 + "~" + "|");
                                        thread2.out_.flush();          
                                }                                   
                                final String ipServer = this.server_.ip_;
                                //this.logDebugMessage(1, "A game server has been deleted, ip: " + ipServer);
                                Main.servers_.remove(ipServer);
                                }
                                break;
                            }                           
                            case 99: {
                                this.server_.servervar2_ = System.currentTimeMillis(); //send server last update
                                break;
                            }
                            case 98: {
                                final String ipServer = this.in_.readLine().trim();
                                //this.logDebugMessage(1, "A game server has been deleted, ip: " + ipServer);
                                Main.servers_.remove(ipServer);
                                this.serverIp_ = null;
                                for (final ClientThread thread : this.server_.ipToClientThread_.values()) {
                                    thread.prepareTermination_ = true;
                                }
                                this.terminateThread();
                                return;
                            }
                            case 96: {
                                final String ipServer = this.in_.readLine().trim();
                                final String ipClient = this.in_.readLine().trim(); 
                                //this.logDebugMessage(1, "A client wishes to connect to a server, client: " + ipClient + ", server: " + ipServer);
                                final ServerInformation info = Main.servers_.getOrDefault(ipServer, null);
                                if (info == null) {
                                    System.out.println("Connection to the server failed, no such server in the server list");
                                    out_.print("*" + 1 + "|" + 1 + "~" + "|");
                                    out_.flush();                                   
                                break;
                                }
                                this.server_ = info;
                                this.server_.ipToClientThread_.put(ipClient, this);
                                this.server_.numplayers_ += 1;
                                //this.logDebugMessage(1, "Connection success");
                                    out_.print("*" + 1 + "|" + 2 + "~" + "|");
                                    out_.flush();
                                break;
                            }
                            case 95: {
                                final String ipClient = this.in_.readLine().trim();
                                this.server_.ipToClientThread_.remove(this);
                                this.server_.numplayers_ -= 1;
                                //this.logDebugMessage(1, String.valueOf(ipClient) + " disconnected from the server at " + this.server_.ip_);
                                this.terminateThread();
                                return;
                            }
                            case 94: {
                                final int parseCount = Integer.parseInt(this.in_.readLine().trim());
                                final StringBuilder msg = new StringBuilder();
                                for (int i = 0; i < parseCount; ++i) {
                                    msg.append(String.valueOf(this.in_.readLine().trim()) + "|");
                                }
                                this.server_.out_.print(msg.toString());
                                this.server_.out_.flush();
                                //this.logDebugMessage(5, "Packet for server: '" + msg.toString() + "'");
                                break;
                            }
                            case 93: {
                                final int parseCount = Integer.parseInt(this.in_.readLine().trim());
                                final String ip = this.in_.readLine().trim();
                                final StringBuilder msg = new StringBuilder();
                                for (int i = 0; i < parseCount - 1; ++i) {
                                    msg.append(String.valueOf(this.in_.readLine().trim()) + "|");
                                }
                                final ClientThread target = this.server_.ipToClientThread_.getOrDefault(ip, null);
                                if (target != null) {
                                    target.out_.print(msg.toString());
                                    target.out_.flush();
                                    //this.logDebugMessage(5, "Packet for " + ip + ": '" + msg.toString() + "'");
                                    break;
                                }
                                //this.logDebugMessage(1, "Packet for " + ip + " failed to send (recipient not found)");
                                break;
                            }
                            case 92: {
                                final int parseCount = Integer.parseInt(this.in_.readLine().trim());
                                final StringBuilder msg = new StringBuilder();
                                for (int j = 0; j < parseCount; ++j) {
                                    msg.append(String.valueOf(this.in_.readLine().trim()) + "|");
                                }
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                    thread2.out_.print(msg);
                                    thread2.out_.flush();
                                }
                                //this.logDebugMessage(5, "Packet for all: '" + msg.toString() + "'");
                                break;
                            }
                            case 91: {
                                final int parseCount = Integer.parseInt(this.in_.readLine().trim());
                                final StringBuilder msg = new StringBuilder();
                                for (int j = 0; j < parseCount; ++j) {
                                    msg.append(String.valueOf(this.in_.readLine().trim()) + "|");
                                }
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                    if (thread2 != this) {
                                        thread2.out_.print(msg);
                                        thread2.out_.flush();
                                    }
                                }
                                //this.logDebugMessage(5, "Packet for others: '" + msg.toString() + "'");
                                break;
                            }
                            case 90: {
                                //this.logDebugMessage(2, "A socket has requested the advanced server list, server list size: " + Main.servers_.size());
                                if (Main.servers_.size()==0) {
                                    this.out_.print("*" + 0 + "|" + 0 + "|" + 0 + "|" + 0 + "|" + 0 + "|" + 0 + "|" + 0 + "~" + "|");   
                                } else {
                                for (final ServerInformation info2 : Main.servers_.values()) {
                                    this.out_.print("*" + String.valueOf(info2.name_) + "|" + info2.ip_ + "|" + info2.description_ + "|" + info2.servervar1_ + "|"  + info2.servervar2_ + "|"  + info2.servervar3_ + "|"  + info2.servervar4_ + "|"  + info2.servervar5_ + "|" + info2.numplayers_ + "|" + info2.ipToClientThread_.size() + "|"  + info2.servervar6_ + "|"  + info2.servervar7_ + "~" + "|");
                                if (System.currentTimeMillis() - info2.servervar2_ >= 1000 * 20) {
                                final String ipServer = info2.ip_;
                                final ServerInformation info = Main.servers_.getOrDefault(ipServer, null);
                                this.server_ = info;                
                                for (final ClientThread thread2 : this.server_.ipToClientThread_.values()) {
                                        thread2.out_.print("*" + 22 + "|" + -1 + "~" + "|");
                                        thread2.out_.flush();
                                }                                   
                                //this.logDebugMessage(1, "A game server has been deleted, ip: " + ipServer);
                                Main.servers_.remove(ipServer);
                                }                                                           
                                }                               
                                }
                                this.out_.flush();
                                break;
                            }
                            case 89: {
                                final String ipServer = this.in_.readLine().trim();
                                final String name = this.in_.readLine().trim(); //Server name
                                final String description = this.in_.readLine().trim(); //class
                                final String servervar1 = this.in_.readLine().trim(); //max players
                                final String servervar3 = this.in_.readLine().trim(); //current lap
                                final String servervar4 = this.in_.readLine().trim(); //total laps
                                final String servervar5 = this.in_.readLine().trim(); //status
                                final String servervar6 = this.in_.readLine().trim(); //Password
                                final String servervar7 = this.in_.readLine().trim(); //Online version
                                //this.logDebugMessage(1, "A game server has been registered, ip: " + ipServer + ", name: " + name + ", description: " + description + ", servervar1: " + servervar1);
                                final ServerInformation gameServer = new ServerInformation(name, servervar1, servervar3, servervar4, servervar5, servervar6, servervar7, ipServer, this.clientSocket_, this.out_, this.in_);
                                gameServer.description_ = description;
                                gameServer.ipToClientThread_.put(ipServer, this);
                                this.server_ = gameServer;
                                Main.servers_.put(ipServer, gameServer);
                                this.serverIp_ = ipServer;
                                break;
                            }
                            default: {
                                //this.logDebugMessage(0, "Unrecognized case: '" + inputLine + "', " + val);
                                break;
                            }
                        }
                    }
                    else if (inputLine.length() > 0) {
                        //this.logDebugMessage(0, "Unformated '" + inputLine + "'");
                        if (this.server_ != null) {
                            this.server_.out_.print(inputLine);
                            this.server_.out_.flush();
                        }
                    }
                    if (this.prepareTermination_) {
                        this.terminateThread();
                        return;
                    }
                    continue;
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
            try {
                this.terminateThread();
            }
            catch (IOException e2) {
                e2.printStackTrace();
            }
        }
    }

    void logDebugMessage(final int requiredVerbose, final String msg) {
        if (Main.verboseLevel_ >= requiredVerbose) {
            System.out.println("[" + this.clientIp_ + "]  " + msg);
        }
    }

    void terminateThread() throws IOException {
        if (!this.terminated_) {
            if (this.serverIp_ != null) {
                Main.servers_.remove(this.serverIp_);
            }
            this.clientSocket_.close();
            this.in_.close();
            this.out_.close();
            //this.logDebugMessage(3, "Cleanup successful");
            this.terminated_ = true;
        }
    }
}
thom_22
  • 33
  • 7
  • Possible duplicate of [java.net.SocketException: Connection reset](https://stackoverflow.com/questions/62929/java-net-socketexception-connection-reset) – M. le Rutte Jun 10 '18 at 10:35

1 Answers1

1

According to JavaDocs#ConcurrentModificationException:

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

And you are removing Main.servers_.remove(ipServer) while iterating on this line:

for (final ServerInformation info2 : Main.servers_.values())

Using ConcurrentHashMap:

Map<String, String> myMap = new ConcurrentHashMap<String, String>();
myMap.put("1", "1");
myMap.put("2", "2");
myMap.put("3", "3");

Iterator<String> it1 = myMap.keySet().iterator();
while (it1.hasNext()) {
    String key = it1.next();
    System.out.println("Map Value:" + myMap.get(key));
    if (key.equals("1")) {
        myMap.remove("3");
        myMap.put("4", "4");
        myMap.put("5", "5");
    }
}

The above could will return:

Map Value:1
Map Value:2
Map Value:4
Map Value:5

Edit: About connection reset:

It seems your input is null, so it gets disconnected badly. You should always handle nullity check within readLine(). Like this (modify to your code):

final String tempInputLine;

if((tempInputLine = this.in_.readLine()) == null ){
    //close
    this.terminateThread();
    return;
}
else{

    final String inputLine = tempInputLine.trim();
    if (ClientThread.numberPattern.matcher(inputLine).matches()){
        //...
    }
    //..
}
Rcordoval
  • 1,792
  • 1
  • 15
  • 24