Java Socket - how to check if the ServerSocket is already in use by another client

3k views Asked by At

I have this client and server programs.

The client get a command from user, and send it to the server, and the server return the result (output of the command) to the client.

I have 2 classes Command and CommandResult (represents the command and the result), and I'm passing them as json with ObjectInput(/Output)Stream.

This is the code:

Client

    Socket socket = new Socket("localhost", 1111);
    ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

    while (true) {
        //get command from user
        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter command:");
        String cmd = scanner.nextLine();
        Command command = new Command(cmd);

        //send the command as json
        String jsonCommand = new Gson().toJson(command);
        out.writeObject(jsonCommand);
        out.flush();

        //get and print the result
        String jsonCommandResult = (String) in.readObject();
        CommandResult commandResult = new Gson().fromJson(jsonCommandResult, CommandResult.class);
        System.out.println(commandResult.getOutput());
    }

Server

    ServerSocket serverSocket = new ServerSocket(1111);
    Socket socket = serverSocket.accept();
    ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

    while (true) {
        //read the command
        String cmd = (String) in.readObject();
        Command command = new Gson().fromJson(cmd, Command.class);

        //run() method will run the command with java Process and return the result
        CommandResult commandResult = command.run();

        //sent the result back to the client as json
        String jsonCommandResult = new Gson().toJson(commandResult);
        out.writeObject(jsonCommandResult);
        out.flush();
    }

Now, this is working fine when I have one client.

But if I'm trying to run a second instance of the client program, while the first one is still in the loop, it hangs in the ObjectInputStream constructor. (third line)

ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

As I understand from the documentation, the constructor block until the corresponding ObjectOutputStream has been created.

Creates an ObjectInputStream that reads from the specified InputStream. A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header.

In my case the server has not accept the new client yet, and therefore the ObjectOutputStream has not been created.

Now, what I want is to throw exception if a new client trying to connect while other client is connected to the same port. But I can't figure it out how to check if the port is now in use before calling this constructor.

1

There are 1 answers

4
avicohh On BEST ANSWER

As @Kayaman proposed in the comments, I created a Runnable class handles the current client.

Then in the server, I'm looping on serverSocket.accept() and allowing only one client at the time by starting a new thread for the first client, and check whether the current client is finished his communication or not.

Here is the final Server class.

public class Server {
    private static final int SERVER_READY = 1;
    private static final int SERVER_BUSY = 0;

    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        int port = Integer.valueOf(args[0]);
        System.out.println(String.format("Starting server on port %s", port));
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server is ready");

        Thread clientThread = null;
        while (true) {
            Socket clientSocket = serverSocket.accept();
            OutputStream os = clientSocket.getOutputStream();
            if (clientThread != null && clientThread.isAlive()) {
                os.write(SERVER_BUSY);
                continue;
            }
            os.write(SERVER_READY);
            System.out.println(String.format("Client connected: %s", clientSocket.getInetAddress()));
            clientThread = new Thread(new ClientWorker(clientSocket));
            clientThread.start();
        }
    }

    public static class ClientWorker implements Runnable {
        private final Socket clientSocket;

        ClientWorker(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }

        @Override
        public void run() {
            try {
                handleClient();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void handleClient() throws IOException, ClassNotFoundException {
            try {
                ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
                ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream());
                while (true) {
                    System.out.println("Waiting for command..");
                    String cmd = (String) in.readObject();
                    System.out.println(String.format("Command received:\n %s", cmd));
                    if (cmd.equals("exit"))
                        break;
                    Command command = new Gson().fromJson(cmd, Command.class);
                    CommandResult commandResult = command.run();
                    String jsonCommandResult = new Gson().toJson(commandResult);
                    System.out.println(String.format("Sending response:\n %s", jsonCommandResult));
                    out.writeObject(jsonCommandResult);
                    out.flush();
                }
                //in case the client connection has closed, we want to end the while loop and accept a new client
            } catch (EOFException | SocketException e) {
            }
            System.out.println("Connection has been closed");
            clientSocket.close();
            System.out.println("Server is ready");
        }
    }
}

In the Client class, I check if the Server is ready or not.

Socket socket = new Socket(HOST, PORT);
int status = socket.getInputStream().read();
if (status != SERVER_READY)
    throw new Exception(String.format("Failed to connect to server %s:%s, Server is busy", HOST, PORT));
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
....
....