3

I'm just getting started with RMI and I'm trying to write a simple program that simulates a train booking system. I have the basics set up - Server, Client, and a Remote object exported. It works fine with one Client connection. However when more than 1 Client connects, the Clients seem to be executing in the same thread. This is the case when I run multiple Clients on the same machine or when I connect a Client from another laptop.

I was under the impression that RMI handled threading on the server side? If not, how do I go about handling multiple Client connections given the code below?

Here are the classes of interest.

Server.....

public class Server {

    public Server() {
        try {
            Booking stub = (Booking) UnicastRemoteObject.exportObject(new BookingProcess(), 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.bind("Booking", stub);
            System.err.println("Server Ready");
        } catch (RemoteException e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
    }

}

BookingProcess.....(I've left out the private methods that processInput(String input) uses)

public class BookingProcess implements Booking {

    private static Journey dublinGalway = new Journey("Dublin to Galway");
    private static Journey dublinLimerick = new Journey("Dublin to Limerick");
    private static Journey dublinCork = new Journey("Dublin to Cork");
    private Journey currentJourney;

    private enum State {
        INITIAL, JOURNEYS_DISPLAYED, JOURNEY_CHOSEN, ANOTHER_BOOKING_OFFERED, SOLD_OUT;
    }

    private State currentState = State.INITIAL;

    public synchronized String processInput(String input) {
        String output = "";

        if(currentState == State.INITIAL) {
            if(bookedOut()) {
                output = "Sorry, there are no seats remaining on any route. Get the bus.";
                currentState = State.SOLD_OUT;
            }
            else {
                output = "Please choose a journey to book: " + "1: " + dublinGalway.getDescription() + ", 2: " + dublinLimerick.getDescription() + ", 3: " + dublinCork.getDescription();
                currentState = State.JOURNEYS_DISPLAYED;
            }
        }

        else if(currentState == State.JOURNEYS_DISPLAYED) {
            output = this.processJourneyChoice(input);
        }

        else if(currentState == State.JOURNEY_CHOSEN) {
            output = "Do you wish to confirm this booking? (y/n)";
            if(input.equalsIgnoreCase("y")) {
                if(bookingConfirmed()) {
                    output = "Thank you. Your journey from " + currentJourney.getDescription() + " is confirmed. Hit return to continue.";
                    //currentState = State.ANOTHER_BOOKING_OFFERED;
                }
                else {
                    output = "Sorry, but the last seat on the " + currentJourney.getDescription() + " route has just been booked by another user.";
                    //currentState = State.ANOTHER_BOOKING_OFFERED;
                }
                currentState = State.ANOTHER_BOOKING_OFFERED;
            }
            else if(input.equalsIgnoreCase("n")) {
                output = "You have cancelled this booking. Hit return to continue.";
                currentState = State.ANOTHER_BOOKING_OFFERED;
            }
        }

        else if(currentState == State.ANOTHER_BOOKING_OFFERED) {
            output = "Would you like to make another booking? (y/n)";
            if(input.equalsIgnoreCase("y")) {
                output = "Hit Return to continue.";
                currentState = State.INITIAL;
            }
            else if(input.equalsIgnoreCase("n")){
                output = "Goodbye.";
                try {
                    Thread.currentThread().join(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                currentState = State.INITIAL;
            }
        }

        else if(currentState == State.SOLD_OUT) {
            output = "Goodbye.";
        }

        return output;
    }

And finally Client......

public class Client {

    public static void main(String[] args) {
        Client client = new Client();
        client.runClient();
    }

    public void runClient() {

        try {
            BufferedReader consoleInput = new BufferedReader(new InputStreamReader(System.in));
            Registry registry = LocateRegistry.getRegistry("localhost");
            Booking stub = (Booking) registry.lookup("Booking");
            String serverResponse = stub.processInput("begin");
            System.out.println("Server: " + serverResponse);

            while((serverResponse = stub.processInput(consoleInput.readLine())) != null) {
                 System.out.println(serverResponse);
                 if(serverResponse.equals("Goodbye.")) {
                        break;
                 }
            }
        } catch (Exception e) {
            System.err.println("Client exception " + e.toString());
            e.printStackTrace();
        }
    }


}
user1061799
  • 95
  • 1
  • 3
  • 12

2 Answers2

4

As for as RMI server threads, the answer is that it may or may not run in a separate thread. See the documentation here:

http://docs.oracle.com/javase/6/docs/platform/rmi/spec/rmi-arch3.html

3.2 Thread Usage in Remote Method Invocations

A method dispatched by the RMI runtime to a remote object implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees with respect to mapping remote object invocations to threads. Since remote method invocation on the same remote object may execute concurrently, a remote object implementation needs to make sure its implementation is thread-safe.

You can take server side thread dumps and you would see that the RMI TCP Connection threads IDs keep changing, however as @jtahlborn noticed the server side method is synchronized so it would execute serially, not necessarily in a single thread though.

Community
  • 1
  • 1
vsnyc
  • 2,117
  • 22
  • 35
  • Thank you for taking the time to answer. Much appreciated. – user1061799 Feb 12 '13 at 19:33
  • The primary implication of this specification is that *you cannot assume it is single-threaded.* – user207421 Feb 12 '13 at 20:32
  • Usually, if current threads are busy, new ones will get created and the client will not be kept waiting. Even a thousand threads could get created if so many requests were received in parallel. Of course, this is not guaranteed as it is not mandated in the spec. But, you can assume for normal purposes clients wont be made to wait. – Teddy Sep 03 '15 at 13:12
1

Your server side processInput() method is synchronized, so, yes, the calls will be handled serially. what does that have to do with RMI?

UPDATE:

if you want to have separate currentState and currentJourney values for each client session, then you need to use the RMI remote session pattern, see this answer for details.

Community
  • 1
  • 1
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • As I've mentioned, Clients seem to execute in the same thread, with or without processInput() being synchronized. For example, I choose some options on a Client connection and I know what the next Server response should be. I then connect another Client expecting to see the initial Server response, but what I actually get is the expected Server response for the first Client connection. This indicates that the Clients are being facilitated by just one BookingProcess thread no? I thought that RMI would create a new thread for each client? Am I mistaken in this? – user1061799 Feb 12 '13 at 19:20
  • @user1061799 - you do realize you are using shared state on the server, right? the `currentState` and `currentJourney` member vars are shared across _all_ client connections. that has nothing to do w/ threads. – jtahlborn Feb 12 '13 at 19:22
  • That was my question yes. So I need a remote object for each client connection or how would you suggest that I handle this? – user1061799 Feb 12 '13 at 19:31