I'm making a chat server using sockets and a MySQL database, and after it's working I want to expand it to become a more complex game server.
I want to know whether my design is missing anything.
Specifically, I'm worried about database access from different threads, or whether I may be creating too many threads, and whether there is likely to be a bottleneck somewhere.
I have 3 functions:
main function starts a 2nd thread for the ServerHandler function that loops and accepts new client connections. That ServerHandler function then opens a new thread for each client connection for a ClientHandler function.
The code/pseudocode (so far incomplete while I'm still considering the architecture) is below. I'm writing it in Scala.
My main questions are regarding the ClientHandler function and whether I'm doing anything in the wrong order. Will I be at risk of separate client threads making database writes & reads in an unexpected order causing unreproducible issues?
I wonder whether I need a separate thread with a list of commands to be executed so I can be sure that 1 client writes then reads, then another client writes then reads, etc. Do I need to maintain some database read/write list? Or is that handled by the database server somehow?
Mostly I'd just like to be enlightened about my lack of understanding of database reads/writes across different threads, and whether there's something obvious that I'm doing wrong here. Does the program structure design look good?
Regarding threads, will I need to test the server with many client connections (1k, 10k, 100k?) and then set some client connection limit to what I think is safe?
// ** ClientHandler **
// This is run in a new thread for each client that connects to the ServerHandler
// This does:
// 1. Set up input and output streams to communicate with 1 client
// 2. Set up connection to database (MySQL server running on the same machine), including:
// a. Establish connection
// b. Read some data from the database (latest version #, # of clients connected, etc)
// 3. Send welcome message to client (latest version #, # of clients connected, etc)
// 4. Set "startTime" variable to current system time in milliseconds to detect client timeout
// 5. Loop and do this (nothing is blocking, so irrelevant steps will be skipped):
// 1. Handle client message (if there is a new one), including:
// a. Parse client message, including:
// i. If we have not verified the client yet, we only accept one command: "connect"
// ii. On "connect", we verify the ID and update the database (most recent log in time)
// b. Database reads/writes/updates as necessary, depending on the command
// c. Send a response message back to the client with the results
// Even if there is no client message received, we do this:
// 2. Check for server-side updates that should be notified to the client, including:
// a. Database reads
// b. Timestamp comparisons, checking when we last notified the client of the server state
// 3. Notify client of any server-side state changes (if necessary)
// 6. If the client times out (~5000ms), update the database that the client has disconnected
// 7. Close database connection
// 8. Close socket
ClientHandler(socket){
// Set up input and output streams to communicate with 1 client
val inputstream = new BufferedReader(new InputStreamReader(socket.getInputStream()))
val outputstream = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
// Set up connection to database (MySQL server running on the same machine)
val db = Database.forConfig("mydb")
// Read some data from the database (latest version #, # of clients connected, etc)
...
// Send welcome message to client (latest version #, # of clients connected, etc)
outputstream.write(...).newLine().flush()
// Set "startTime" variable to current system time in milliseconds to detect client timeout
var startTime = System.currentTimeMillis()
//
...
}
// ** ServerHandler **
// This runs once in its own thread (because it contains a blocking call)
// This creates a new thread to run ClientHandler for each client that connects
ServerHandler(server_socket){
while(true){
val socket = server_socket.accept() // this is a blocking call
val client_handler = new ClientHandler(socket)
val thread = new Thread(client_handler)
thread.start() // when a client connects, start a new thread to handle that client
}
}
// ** main **
// This does 3 things:
// 1. Start a new thread for ServerHandler which sits and waits for clients to connect
// 2. Loop and accept commands from admin via console
// 3. Process server-side logic in real-time
main{
// Start a new thread for ServerHandler
val server_socket = new ServerSocket(port 10000)
val server_handler = new ServerHandler(server_socket)
val thread = new Thread(server_handler)
thread.start() // start a thread for the server handler
// Loop and accept commands from admin via console
while(true){
input match{
case "stats" =>
// print stats (# of clients logged in etc)
case "quit" =>
// close server_socket etc and stop program
case _ =>
// unrecognized command
}
}
}