1

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
        }
    }
}
3
  • 1
    In general, you can expect an ACID database to be thread safe and perform well with concurrent operations en.wikipedia.org/wiki/ACID Now your question is very broad. I would suggest being more specific with your scalability target, because the answers are very different at 1k, 10k and 100k concurrent clients
    – Martin K
    Feb 15, 2020 at 21:49
  • @MartinK My goal is 100k, which is why I'm writing it in Scala and am focusing on performance and scalability, but I'll still do testing at various loads because I will need to know what the server can handle regardless of my goal Feb 16, 2020 at 6:03
  • 1
    @Gimme the 411 if your aim is to have such a huge number of users try to see if you can construct the system so that you can keep adding more server instances rather than forcing one poor machine to handle it all. Concepts such as eventual consistency and geographical partitioning comes to mind. I assume that the chat/game will have more than 1 room and then you could let each server instance handle a bunch of rooms each with servers colaborating about clients mooving between rooms. If you are hosting this in the cloud you could get this to auto scale, if you have the income to pay for it.
    – lijat
    Feb 16, 2020 at 6:15

1 Answer 1

2

Databases have been designed to be accessed from multiple threads for a long time. Try googling about how to use transactions in databases that should help you on how to ensure consistency in the face of concurent read and write.

Also scince you are using mysql, ensure that you are not using the older myisam backend, it has missing support for a lot of consistency features. The newer innodb backend does not share thouse issues.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.