I am creating a chat server using sockets and a MySQL database, and after it works, I want to expand it to make it a more complex game server.
I want to know if my design is missing something.
Specifically, I am concerned about accessing the database from different threads, or if I may be creating too many threads and if there is likely to be a bottleneck somewhere.
I have 3 functions:
principal function start a second thread for the ServerHandler function that repeats and accepts new customer 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 am still considering the architecture) is below. I am writing it in Scala.
My main questions are about the ClientHandler function and if I am doing something in the wrong order. Will I be at risk of separate client threads performing database writes and reads in an unexpected order that cause irreproducible problems?
I wonder if I need a separate thread with a list of commands to execute, so I can be sure that 1 client writes and then reads, then another client writes and then reads, etc. Do I need to maintain a read / write list of the database? Or does the database server handle it in some way?
Mainly, I would like to be aware of my lack of understanding of database readings / writes in different threads, and if there is something obvious that I am doing wrong here. Does the design of the program structure look good?
With regard to threads, will I have 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
// 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)
// 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
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
// 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
case "stats" =>
// print stats (# of clients logged in etc)
case "quit" =>
// close server_socket etc and stop program
case _ =>
// unrecognized command