Search code examples
javaclientmud

What is the best way to manage text-based client-server connections?


I'm looking to write a small client-server-based text game that handles multiple client connections and persistently affects a game state. I'm wondering what the best way would be to handle multiple connects such that commands are processed in the order they arrive at the server.

Ideally I'm not looking to take advantage of multi-threading, at least on the command processing level. I would be okay with each client having a separate thread (in order to have blocking IO on each thread), as long as I could unify the processing in a single thread thereafter.

Since the only communication between the client and server will be text, I'm not sure how best to go about setting up the communication. If I chose blocking IO, how would I get queue the processing to occur in a single thread?

Alternatively, if I choose non-blocking IO and use a selector to query for when clients have written to the server, how can I get read a String of unknown/unlimited length without using a set-size ByteBuffer? Non-blocking also favours keeping the processing in a single thread as it can just read from the client connections as and when they send new data. However, when I tried to implement it with read/writeUTF I came up against the IllegalBlockingModeException heh.

Any answers to the questions or suggestions on how to do this in a way I haven't mentioned would be sincerely appreciated! I'm fairly new to clients and servers so I don't know whether java.io or java.nio would be most appropriate.

Sorry for the convoluted question. I think I ran away with myself.


Solution

  • Opinions differ, but I'd definitely go with a single thread per client. The communication to the single processing thread could then go via a LinkedBlockingQueue, or just a synchronized LinkedList.

    Something like this on the per-client thread:

    public class Client implements Runnable, ResponseOutput {
    
        private final BufferedReader br;
        private final PrintWriter pw;
    
        public Client(Socket s) {
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            pw = new PrintWriter(s.getOutputStream());
        }
    
        // defined by the ResponseOutput interface
        public void sendReply(String reply) {
            pw.println(reply);
        }
    
        public void run() {
            try {
                while (true) {
                    String s = br.readLine();
                    if (s==null)
                        break;
                    Processor.queue(new Processor.InputItem(this, s));
                }
            } catch (IOException ioe) {
                ... error handling ...
            }
        }
    }
    

    Then this for the processing:

    public class Processor implements Runnable {
        static public class InputItem {
            final ResponseOutput client;
            final String command;
    
            public InputItem(ResponseOutput client, String command) {
                this.client = client;
                this.command = command;
            }
        }
    
        static private Processor instance;
        static public void queue(InputItem item) {
            instance.commandQueue.add(item);
        }
    
        private BlockingQueue<InputItem> commandQueue;
    
        public void run() {
            try {
                while (true) {
                    InputItem item = commandQueue.take();
                    String reply = doStuff(item.command);
                    item.client.sendReply(reply);
                }
            } catch (InterruptedException ie) {
                ... error handling ....
            }
        }
    }
    

    Within the InputItem class, you can also include a reference to any game state that needs updating. Since there's only the processing thread changing it, you get to do that without any synchronization.