Multithreading Socket communication Client/Server
Asked Answered
M

4

7

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?

Marilynnmarimba answered 25/9, 2012 at 17:53 Comment(0)
L
12

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");
            }
        }        
    }
}
Lurette answered 25/9, 2012 at 18:3 Comment(4)
Couple of comments: your shutdownNow() will not kill the server since accept() ignored interrupts. Closing the server socket is the right way here. startServer() should really be runServer() because it never returns. Having the start and stop methods is misleading. Why submit a Callable if it is never used? The socket.close() should be inside of a try / finally block. Same for the server accept loop.Dick
Hey basiljames I don't know if you saw my last post bellow, but I'm having little trouble following the example you posted. How would I go about implementing this into my program as I've never used ExecutorService before.Marilynnmarimba
@Marilynnmarimba Sorry for not replyng. I had to leave home. Seems Gray have cleared your doubts. Let me know if any clarifications are needed.Lurette
I've tried to use this in a server, but the output stream doesn't work. serverSocket = new ServerSocket(serverPort); PrintWriter output = new PrintWriter(socket.getOutputStream(), true); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); while(input.readLine() != null) { output.println(pro.processInput(input.readLine())); }Westbrooke
D
4

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();
        }
    }
Dick answered 25/9, 2012 at 19:33 Comment(6)
How would I fork the thread to handle each client connection? Do I just start new thread with new Thread(new Runnable()) and pass the Client socket as paramaters? I think this is the approach I need to take since I don't really understand ExecutorService.Marilynnmarimba
Take the time to understand ExecutorService @Nick. It is recommended as a replacement for most new Thread() scenario. That said, I've added to my answer how to do it with raw threads.Dick
I will do some reading on ExecutorService for sure but for now I am being asked to deal with Threads. Also would I be able to specify how many threads I want to create since this will all be ran from a single Client program? Really appreciate your help, I'm new to Threading and trying to understand it.Marilynnmarimba
This will create 1 thread per client. If you want to control how many clients can connect to your server at the same time, then this is much harder. Using a Executors.newFixedThreadPool(numThreads) to define your thread-pool will limit the number of threads but not the number of connections.Dick
Basically I'm asking server to do one low load operation and one high load operation. Low being asking it to return current date and time and high load being something like free memory and take time measurements of how long it takes to execute each of those. What I want to do is create Client threads so that each thread/client takes its own measurements of those two.Marilynnmarimba
Did it work @Nick? Be sure to +1 my answer if it helped and accept it if appropriate.Dick
L
2

Change this: public void startServer() throws IOException To this: public void startServer(Socket clientSocket) throws IOException

Then all you need to do is:

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

    try
    {
        while(true) {
            final Socket socket = serverSocket.accept();
            new Thread(new Runnable() {
                public void run() {
                    try {
                        startServer(socket);
                    } catch(IOException e) {e.printStackTrace();}
                }
            }).start();
        }
    }
    catch(IOException e)
    {
        System.err.println("Accept failed");
        System.exit(1);
    }
}

And lastly, you can remove private Socket clientSocket = null;

That should get you there. Or at least pretty close.

Lalla answered 25/9, 2012 at 18:1 Comment(3)
This works, but is there a way I can make it so that Client can specify how many threads they want to run? For example right now I have just multiple Client programs running, but I want to make it where I can just have one Client program run but can create multiple threads from all of which perform same function.Marilynnmarimba
Yes, that's thread pool management. Look at the response from @basiljames. Basically you create a pool: ExecutorService pool = Executors.newFixedThreadPool(5); then you submit work to it instead of creating a new thread.Lalla
I'm having trouble following the code he posted, one you have up I can understand. Is there any way you can show me how that would translate into what I have? Thanks again!Marilynnmarimba
M
0
private static final int SERVER_PORT = 35706;
private ServerSocket serverSocket;
private final ArrayList<ClientThread> activeClients = new ArrayList<>();

public void startServer() {

    try {
        serverSocket = new ServerSocket(SERVER_PORT);
        
        final ExecutorService clientPool = Executors.newCachedThreadPool();

        while (!serverSocket.isClosed()) {

            try {
                Future<Socket> future = clientPool.submit(() -> {
                       Socket socket = serverSocket.accept();
                       ClientThread clientThread= new ClientThread(socket);
                       return (socket);
                });

                activeClients.add(future.get());
            } catch (IOException e) {
                clientPool.shutdownNow();
                System.out.println(e.getMessage());
            } catch (InterruptedException | ExecutionException e) {
                System.out.println(e.getMessage());
            }
        }

    } catch (IOException e) {
        System.out.println(e.getMessage());
    }
}



public void stopServer() {  

   try {
        serverSocket.close();
        activeClients.forEach(socket -> {
            try {
                socket.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        });
            
   } catch (IOException ex) {
        System.out.println(e.getMessage());
   }

}



private static class ClientThread implements Runnable{
    private final Socket socket;

    public ClientThread(Socket socket) throws IOException {
       this.socket = socket;
    }
        
    @Override
    public void run() {
        /* Your implementation */
    }
}
Mouthwatering answered 29/8, 2020 at 11:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.