Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multithreading Socket communication Client/Server

I finished writing a Client/Server Socket communication program that works fine. Now I'm trying to figure out how to make it so that I can have multiple Client connections to the Server at once. I've looked around and there seems to be more than a couple of different ways to do this. so I've come here to ask you guys for help/suggestions.

My Server:

public class Server {
    private ServerSocket serverSocket = null;
    private Socket clientSocket = null;

    public Server() {
        try {
            serverSocket = new ServerSocket(7003);
        } catch (IOException e) {
            System.err.println("Could not listen on port: 7003");
            System.exit(1);
        }

        try {
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            System.err.println("Accept failed");
            System.exit(1);
        }
    }

    public void startServer() throws IOException {
        PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
        BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

        String inputLine, outputLine;

        outputLine = "Connected to Server";
        output.println(outputLine);

        while ((inputLine = input.readLine()) != null) {
            // This just determines users input and server ruturns output based on that

            outputLine = this.getServerOutput(inputLine);
            output.println(outputLine);

            if (outputLine.equals("Bye"))
                break;
        }

        output.close();
        input.close();
        clientSocket.close();
        serverSocket.close();
    }
}

Would I need to make my constructor create threads and startServer() or would be my run method?

like image 875
Nick Avatar asked Sep 25 '12 17:09

Nick


2 Answers

You should use ExecutorService. Your client request processing would be the run() of a Runnable and after each accept you can call ExecutorService.submit(runnableTask) to asynchronously service the client.

A sample using ExecutorService.

public class MyServer {

    private static MyServer server; 
    private ServerSocket serverSocket;

    /**
     * This executor service has 10 threads. 
     * So it means your server can process max 10 concurrent requests.
     */
    private ExecutorService executorService = Executors.newFixedThreadPool(10);        

    public static void main(String[] args) throws IOException {
        server = new MyServer();
        server.runServer();
    }

    private void runServer() {        
        int serverPort = 8085;
        try {
            System.out.println("Starting Server");
            serverSocket = new ServerSocket(serverPort); 

            while(true) {
                System.out.println("Waiting for request");
                try {
                    Socket s = serverSocket.accept();
                    System.out.println("Processing request");
                    executorService.submit(new ServiceRequest(s));
                } catch(IOException ioe) {
                    System.out.println("Error accepting connection");
                    ioe.printStackTrace();
                }
            }
        }catch(IOException e) {
            System.out.println("Error starting Server on "+serverPort);
            e.printStackTrace();
        }
    }

    //Call the method when you want to stop your server
    private void stopServer() {
        //Stop the executor service.
        executorService.shutdownNow();
        try {
            //Stop accepting requests.
            serverSocket.close();
        } catch (IOException e) {
            System.out.println("Error in server shutdown");
            e.printStackTrace();
        }
        System.exit(0);
    }

    class ServiceRequest implements Runnable {

        private Socket socket;

        public ServiceRequest(Socket connection) {
            this.socket = connection;
        }

        public void run() {

            //Do your logic here. You have the `socket` available to read/write data.

            //Make sure to close
            try {
                socket.close();
            }catch(IOException ioe) {
                System.out.println("Error closing client connection");
            }
        }        
    }
}
like image 135
basiljames Avatar answered Nov 12 '22 11:11

basiljames


how to make it so that I can have multiple Client connections to the Server at once

Right now you are starting your server and immediately waiting for a single client to connect in the constructor.

clientSocket = serverSocket.accept();

Then you handle that single socket connection inside of your startServer() method. This means that no other clients will be handled.

public void startServer() throws IOException {
    PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
    ...

Typically with a server pattern like this, you would do something like the following:

  1. Setup your server socket in the constructor.
  2. Create an acceptClients() method which would loop waiting for a client to be accepted. This could fork a thread to accept the clients in a thread of its own in the background.
  3. For each client, either fork a thread to handle the connection, passing the thread the clients socket. Better would be to, as @basiljames shows, use an ExecutorService to manage the threads for you.

Here's some sample code:

public class Server {
    private ServerSocket serverSocket = null;

    public Server(int portNumber) throws IOException {
        serverSocket = new ServerSocket(portNumber);
    }

    // this could be run in a thread in the background
    public void acceptClients() throws IOException {
        // create an open ended thread-pool
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // wait for a client to connect
                Socket clientSocket = serverSocket.accept();
                // create a new client handler object for that socket,
                // and fork it in a background thread
                threadPool.submit(new ClientHandler(clientSocket));
            }
        } finally {
            // we _have_ to shutdown the thread-pool when we are done
            threadPool.shutdown();
        }
    }

    // if server is running in background, you stop it by killing the socket
    public void stop() throws IOException {
        serverSocket.close();
    }

    // this class handles each client connection
    private static class ClientHandler implements Runnable {
        private final Socket clientSocket;
        public ClientHandler(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
        public void run() {
            // use the client socket to handle the client connection
            ...
        }
    }
}

Using the ExecutorService thread-pools is recommended for just about all Thread implementations like this. If, however, you are stuck to using raw Thread for some reason, you can do the following instead in your acceptClients() method:

    public void acceptClients() throws IOException {
        while (!Thread.currentThread().isInterrupted()) {
            // wait for a client to connect
            Socket clientSocket = serverSocket.accept();
            // fork a background client thread
            new Thread(new ClientHandler(clientSocket)).start();
        }
    }
like image 5
Gray Avatar answered Nov 12 '22 12:11

Gray