I'm trying to make a backend for QuizUp like application: user connects to a server, sends credentials and gets paired up with another user. After that server handles each pair, periodicaly sending server messages to each user in a pair and also redirecting user's mesages between them.
Server class:
private static class Server{
private static final int NUM_THREADS = 2400;
private ExecutorService executorService;
private ServerSocket serverSocket;
private int listeningPort;
public volatile boolean isRunning;
private Thread mainThread;
private volatile Map<String, Conn> playRequests;
public Server(int port){
try {
executorService = Executors.newFixedThreadPool(NUM_THREADS);
listeningPort = port;
serverSocket = new ServerSocket(listeningPort);
isRunning = true;
playRequests = new ConcurrentHashMap<String, Conn>();
mainThread = new Thread(new Runnable(){
@Override
public void run() {
handleIncomingConnections();
}
});
} catch (IOException e) {
System.out.println(e.toString());
}
}
public void run(){
mainThread.start();
}
private void handleIncomingConnections(){
while(isRunning){
try {
final Socket client = serverSocket.accept();
Runnable gameRunnable = new Runnable(){
@Override
public void run() {
try{
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
String read = null;
String id = null;
boolean isRequesting = false;
String rid = null;
while(!(read = reader.readLine()).equals("FIN_1")){
String[] str = read.split("#");
if(str[0].equals("id")){
id = str[1];
}else if(str[0].equals("isRequesting")){
isRequesting = (str[1].equals("1"));
}else if(str[0].equals("rid")){
rid = str[1];
}
}
Conn connection = new Conn(client, isRequesting, id, writer, reader);
if(isRequesting){
playRequests.put(rid, connection);
}else{
if(playRequests.containsKey(id)){
Conn conn = playRequests.get(id);
playRequests.remove(id);
handleGame(conn, connection);
}
}
}catch(Exception e){
System.out.println(e.toString());
}
}
};
executorService.execute(gameRunnable);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void handleGame(Conn a, Conn b){
new GameHandler(a, b).execute();
}
}
GameHandler class:
private class GameHandler{
private volatile Conn a;
private volatile Conn b;
private Thread aReadThread;
private Thread bReadThread;
private Thread messageThread;
private Runnable aReadRunnable;
private Runnable bReadRunnable;
private Runnable messageRunnable;
private volatile PrintWriter aWriter;
private volatile PrintWriter bWriter;
private volatile BufferedReader aReader;
private volatile BufferedReader bReader;
private volatile boolean aIsReady;
private volatile boolean bIsReady;
private volatile boolean isGameRunning;
public GameHandler(final Conn s1, final Conn s2){
this.a = s1;
this.b = s2;
isGameRunning = true;
try {
aWriter = a.writer;
bWriter = b.writer;
aReader = a.reader;
bReader = b.reader;
} catch (Exception e) {
try {
isGameRunning = false;
a.close();
b.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println(e.toString());
}
messageRunnable = new Runnable(){
@Override
public void run() {
System.out.println(a.id + " " + b.id);
messageThread = Thread.currentThread();
for(int i = 0; i < 6; i++){
if(isGameRunning){
try{
Thread.sleep(4000);
}catch(InterruptedException e){
}
}
}
//end game
isGameRunning = false;
try {
a.close();
b.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
aReadRunnable = new Runnable(){
@Override
public void run() {
aReadThread = Thread.currentThread();
String line = null;
try {
while (isGameRunning && (line = aReader.readLine()) != null && !(line = aReader.readLine()).equals("FIN")){
bWriter.println(line);
}
a.close();
System.out.println(a.id + " done");
} catch (Exception e) {
try {
isGameRunning = false;
a.close();
b.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println(e.toString());
}
}
};
bReadRunnable = new Runnable(){
@Override
public void run() {
bReadThread = Thread.currentThread();
String line = null;
try {
while (isGameRunning && (line = bReader.readLine()) != null && !(line = bReader.readLine()).equals("FIN")){
aWriter.println(line);
}
b.close();
System.out.println(b.id + " done");
} catch (Exception e) {
try {
isGameRunning = false;
a.close();
b.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println(e.toString());
}
}
};
}
public void execute() {
executorService.execute(messageRunnable);
executorService.execute(aReadRunnable);
executorService.execute(bReadRunnable);
}
}
And a container class for each users to hold open socket, in/out streams, credentials, etc:
private class Conn{
public Socket s;
public boolean isRequesting;
public PrintWriter writer;
public String id;
public BufferedReader reader;
Conn(Socket s, boolean isRequesting, String id, PrintWriter writer, BufferedReader reader){
this.s = s;
this.isRequesting = isRequesting;
this.id = id;
this.writer = writer;
this.reader = reader;
}
public void close() throws IOException{
s.close();
}
}
The logic is following:
Server has a mainThread
, where it accepts incoming connections and creates client sockets. For each new socket it creates a gameRunnable
, where it listens for client's credentials (whether this client is the one requesting connection, id of the user it wants to connect to, id of itself). After receiving credentials, server creates a new Conn object, storing all the info(id, and also socket and in/out streams, so it doesn't have to open it again after) there, and than places it in the Map (playRequests
) with requested user id as a key. If there is a matching pair in a map, server creates a new GameHandler
for these two Conn objects (all this still goes inside the gameRunnable
). Each GameHandler
contains three Runnables: messageRunnable to send messages from server to both users, and two Runnables (aReadRunnable
and bReadRunnable
) to read incoming data from both sockets. So basically, each communication session (game) requires 4 threads (1 to get credentials and start a game, and three to maintain the game before the end). Here are the questions I have:
Are there any design/implementation issues you see here? Please be as picky as possible because I'd really not want it to crash under high load. If you see smth, you are more than welcome to give your solutions
I know that having large and uncontrolled number of threads is a bad practice, so I'm using an executer with fixed thread pool to execute all the Runnables. However, due to the game features, I can't make users who are requesting connections wait for empty threads in a pool, what is obviously going to happen if I have a lot of incoming connections. So is usage of thread pool reasonable here? If yes, what number of threads should I use, given that I need 4 threads per game, and each game lasts approximately 2 minutes.
Am I closing all the sockets correctly? Are there any memory leaks?
Other questions regard server deploying:
I'm planning to run it on Amazon EC2. Should I use Tomcat server for this, or can I just run it as a plain java program on JVM?
I tested it on my laptop, and having many simultaneous connections, heap size is not enough to handle all of them. Should I increase heap size before lunch as much as possible, or it may affect performance?