0

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.

avicohh
  • 177
  • 1
  • 3
  • 11
  • Can you use multiple server threads, otherwise it will have to wait? – jrtapsell Sep 14 '17 at 16:36
  • Why do you only accept a single connection to the server at a time? You could always close the `ServerSocket` after accepting a connection. – Kayaman Sep 14 '17 at 16:38
  • I don't want to allow more then one client. – avicohh Sep 14 '17 at 16:41
  • @Kayaman Is there an edge case that means you can't reuse the server socket, while passing the accepted sockets to different threads for handling? – jrtapsell Sep 14 '17 at 16:43
  • @avicohh what do you want clients to do when the server is busy? – jrtapsell Sep 14 '17 at 16:45
  • @jrtapsell No, I meant that if he wants to allow only a single client, he can close the `ServerSocket`. No worries about a second client trying to connect (well, they can *try*). – Kayaman Sep 14 '17 at 16:45
  • I see what you mean, wouldn't that make it impossible to tell between server busy and server doesn't exist (might be a feature I guess) – jrtapsell Sep 14 '17 at 16:48
  • 1
    @jrtapsell Well sure, but the requirements here are pretty vague. Why would you want a single user server at this day and age. He could also accept the connection and close it rightaway if the other user is still doing something (requiring a single additional thread). – Kayaman Sep 14 '17 at 16:52
  • @Kayaman now I understand what you meant. This is just poc project, next I intend to integrate it into another automation project in our company, and I can't allow to clients sending commands to the same machine together. About what you proposed, I do want to other clients connect after the first client close his connection, and therefore I don't close the serversocket. (There is another loop reaccepting new client after the first is done) – avicohh Sep 14 '17 at 16:56
  • Well I gave you two options. I didn't say that you're not allowed to reopen the `ServerSocket`. – Kayaman Sep 14 '17 at 16:58
  • Is that mean I will have to initiallize the ServerSocket for each new client (new..), or there is a way to just open it again? And I do want to tell the other client the sever is busy – avicohh Sep 14 '17 at 17:05
  • 1
    Then you should probably keep the serversocket open and just tell the incoming "extra" connection that he's not welcome. You do need an extra thread in that case. The main thread will loop on `accept()` and the additional thread will handle the first client. – Kayaman Sep 14 '17 at 17:11
  • Good idea. I will try it at work on Sunday. Thx. – avicohh Sep 14 '17 at 17:20

1 Answers1

0

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());
....
....
avicohh
  • 177
  • 1
  • 3
  • 11
  • The easy way to do this is not to use threads at all. Just do everything inn the same thread that is calling `accept()`. Unclear why you would want this, but that is how to do it. – user207421 Sep 19 '17 at 10:28
  • Please read the comments above. The reason I can't use the same thread is because it will remain in the handling client loop, and never reach the `accept()` again. – avicohh Sep 19 '17 at 10:36
  • That is exactly what you said you wanted. One client at a time. – user207421 Sep 22 '17 at 23:44
  • Not exactly. I do wan't tell the other clients the server is busy, and I can't do that while I handling the first client. (Unless I open new thread for the current client, and handle the new connections from the main thread) – avicohh Sep 23 '17 at 16:34