Lecture Notes Twenty-Three: Building A Chat Application in Java

Let's get started again.

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman
burrowww.cs.indiana.edu% mkdir rmi2001
burrowww.cs.indiana.edu% cd rmi2001
burrowww.cs.indiana.edu% ls
burrowww.cs.indiana.edu% 
The basic starting point.

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
burrowww.cs.indiana.edu% ls -ld *
-rw-r--r--   1 dgerman  faculty       386 Nov 13 09:48 Client.java
-rw-r--r--   1 dgerman  faculty       263 Nov 13 09:48 Server.java
burrowww.cs.indiana.edu% cat Server.java
public class Server {
    public String remoteMethod() {
        try {
            return " the time is " + new java.util.Date() + "\n" + 
                " on " + java.net.InetAddress.getLocalHost() + "\n"; 
        } catch (Exception e) {
            return "Server error: " + e + " ...\n"; 
        }
    }
}
burrowww.cs.indiana.edu% cat Client.java
public class Client {
    public static void main(String[] args) {
        try {
            String time = null; 
            Server remote = new Server(); 
            System.out.println(
                               "Client reporting from: (" +
                               java.net.InetAddress.getLocalHost() +
                               ") \n" + remote.remoteMethod()); 
        } catch (Exception e) {
            System.out.println("Client error: " + e + "...\n"); 
        }
    }
}
burrowww.cs.indiana.edu%
Let's see it running.

burrowww.cs.indiana.edu% javac *.java
burrowww.cs.indiana.edu% java Client
Client reporting from: (burrowww.cs.indiana.edu/129.79.245.98) 
 the time is Tue Nov 13 09:51:10 EST 2001
 on burrowww.cs.indiana.edu/129.79.245.98

burrowww.cs.indiana.edu% java Client
Client reporting from: (burrowww.cs.indiana.edu/129.79.245.98) 
 the time is Tue Nov 13 09:51:14 EST 2001
 on burrowww.cs.indiana.edu/129.79.245.98

burrowww.cs.indiana.edu% 
Easy, but notice one thing: the client controls everything. We do not need to start the server first. But in a networked context that will change, and we will have to bring the server up before we can run the client. Let's now write the networked version.

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
burrowww.cs.indiana.edu% ls -ld N*.java
-rw-r--r--   1 dgerman  faculty       474 Nov 13 10:28 NClient.java
-rw-r--r--   1 dgerman  faculty       889 Nov 13 10:27 NServer.java
-rw-r--r--   1 dgerman  faculty       122 Nov 13 10:30 NServerAd.java
burrowww.cs.indiana.edu% cat NServerAd.java
public interface NServerAd extends java.rmi.Remote {
    public String remoteMethod() throws java.rmi.RemoteException; 
}
Make a note of this link.

burrowww.cs.indiana.edu% cat NServer.java
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class NServer 
    extends UnicastRemoteObject 
    implements NServerAd 
{
    public String remoteMethod() {
        try {
            return " the time is " + new java.util.Date() + "\n" + 
                " on " + java.net.InetAddress.getLocalHost() + "\n"; 
        } catch (Exception e) {
            return "Server error: " + e + " ...\n"; 
        }
    }

    public NServer() throws RemoteException { 
        System.out.println("Server initialized... "); 
    }

    public static void main(String[] args) {
        Registry reg;
        try {
            System.setSecurityManager(new RMISecurityManager()); 
            NServer richard = new NServer();
            reg = LocateRegistry.createRegistry(31287); 
            reg.bind("Feynman", richard); 
            System.out.println("Server ready... "); 
        } catch (Exception e) {
            System.out.println("Server error " + e + "...\n"); 
        }
    }

}
burrowww.cs.indiana.edu% cat NClient.java
import java.rmi.*; 

public class NClient {
    public static void main(String[] args) {
        try {
            String time = null; 

            NServerAd farAway = 
                (NServerAd)Naming.lookup(
                    "rmi://" + args[0] + ":31287/Feynman"); 

            System.out.println(
                "Client reporting from: (" +
                 java.net.InetAddress.getLocalHost() +
                 ") \n" + farAway.remoteMethod()
            );

        } catch (Exception e) {
            System.out.println("Client error: " + e + "...\n"); 
        }
    }
}
burrowww.cs.indiana.edu%
We try again, like last time.

It works now. So what's different?

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
burrowww.cs.indiana.edu% ls -ld ~/.java*
-rw-r--r--   1 dgerman  faculty        72 Nov  9 23:08 /u/dgerman/.java.policy
burrowww.cs.indiana.edu% cat ~/.java*
grant {
  permission java.net.SocketPermission "*", "connect,accept";
};burrowww.cs.indiana.edu%
Rob Henderson Where did we get this file?

(Many thanks to Rob Henderson for researching, and helping us with, this question).

If you ever wondered what's the advantage of being part of a team you now have the answer.

So the question now is: where did we get this file?

Well, things have changed since November 1999, the date of the previous tutorial.

For one thing, as before, loading classes remotely needs a security manager. The default Java security policy, however, does not allow all the networking operations required to resolve a class from a remote host. (See Flanagan, see Oaks) So we need to have this specified somewhere.

Let's investigate this more closely. We work with other burrow machines.

These sessions are intertwined, so check the time stamps to see what happened.

On the server side:

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
burrowww.cs.indiana.edu% date
Tue Nov 13 11:16:27 EST 2001
burrowww.cs.indiana.edu% ls -ld ~/.java*
-rw-r--r--   1 dgerman  faculty        72 Nov  9 23:08 /u/dgerman/.java.policy
burrowww.cs.indiana.edu% cat ~/.java.policy
grant {
  permission java.net.SocketPermission "*", "connect,accept";
};burrowww.cs.indiana.edu% date
Tue Nov 13 11:16:59 EST 2001
burrowww.cs.indiana.edu% java NServer
Server initialized... 
Server ready... 
^Cburrowww.cs.indiana.edu% 
burrowww.cs.indiana.edu% date
Tue Nov 13 11:17:59 EST 2001
burrowww.cs.indiana.edu% mv ~/.java.policy ~/trickortreat
burrowww.cs.indiana.edu% date
Tue Nov 13 11:19:01 EST 2001
burrowww.cs.indiana.edu% java NServer
Server initialized... 
Server ready... 
^Cburrowww.cs.indiana.edu% 
burrowww.cs.indiana.edu% date
Tue Nov 13 11:19:25 EST 2001
burrowww.cs.indiana.edu% 
On the client side:
tucotuco.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
tucotuco.cs.indiana.edu% date
Tue Nov 13 11:16:34 EST 2001
tucotuco.cs.indiana.edu% java NClient burrowww.cs.indiana.edu
Client reporting from: (tucotuco.cs.indiana.edu/129.79.245.110) 
 the time is Tue Nov 13 11:17:53 EST 2001
 on burrowww.cs.indiana.edu/129.79.245.98

tucotuco.cs.indiana.edu% date
Tue Nov 13 11:19:08 EST 2001
tucotuco.cs.indiana.edu% java NClient burrowww.cs.indiana.edu
Client error: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: 
        java.net.ConnectException: Connection refused...

tucotuco.cs.indiana.edu% 
So the file is truly important.

Here, however, is another way of doing the same thing.

On the server side (sessions are still intertwined):

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/rmi2001
burrowww.cs.indiana.edu% ls -ld ../.java*
No match
burrowww.cs.indiana.edu% ls -ld ../trick*
-rw-r--r--   1 dgerman  faculty        72 Nov  9 23:08 ../trickortreat
burrowww.cs.indiana.edu% cat ../trick*
grant {
  permission java.net.SocketPermission "*", "connect,accept";
};burrowww.cs.indiana.edu% date
Tue Nov 13 11:33:20 EST 2001
burrowww.cs.indiana.edu% date; java -Djava.security.policy=../trickortreat NServer
Tue Nov 13 11:34:10 EST 2001
Server initialized... 
Server ready... 
^Cburrowww.cs.indiana.edu% date; java NServer
Tue Nov 13 11:34:48 EST 2001
Server initialized... 
Server ready... 
^Cburrowww.cs.indiana.edu%
On the client side:
tucotuco.cs.indiana.edu% pwd; date
/nfs/paca/home/user1/dgerman/rmi2001
Tue Nov 13 11:33:27 EST 2001
tucotuco.cs.indiana.edu% date; java NClient burrowww.cs.indiana.edu
Tue Nov 13 11:34:15 EST 2001
Client reporting from: (tucotuco.cs.indiana.edu/129.79.245.110) 
 the time is Tue Nov 13 11:34:36 EST 2001
 on burrowww.cs.indiana.edu/129.79.245.98

tucotuco.cs.indiana.edu% date; java NClient burrowww.cs.indiana.edu 
Tue Nov 13 11:34:58 EST 2001
Client error: java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: 
        java.net.ConnectException: Connection refused...

tucotuco.cs.indiana.edu%
So we can bypass the naming convention, but we can't do without doing something.

Now let's try a more sophisticated development.

burrowww.cs.indiana.edu% pwd
/nfs/paca/home/user1/dgerman/chat2001
burrowww.cs.indiana.edu% ls -ld *.java
-rw-r--r--   1 dgerman  faculty      1469 Nov 13 14:17 ChatClient.java
-rw-r--r--   1 dgerman  faculty      1138 Nov 13 16:08 ChatServer.java
-rw-r--r--   1 dgerman  faculty       135 Nov 13 16:07 ClientExports.java
-rw-r--r--   1 dgerman  faculty       208 Nov 13 16:08 ServerExports.java
burrowww.cs.indiana.edu% cat ClientExports.java
import java.rmi.*;

public interface ClientExports extends Remote {
    public void update(String message) throws RemoteException; 
}

burrowww.cs.indiana.edu% cat ServerExports.java
import java.rmi.*;

public interface ServerExports extends Remote {
    public void register(ClientExports client) throws RemoteException;
    public void broadcast(String message) throws RemoteException; 
}
burrowww.cs.indiana.edu% cat ChatClient.java
import java.rmi.*;
import java.rmi.server.*; 
import java.io.*; 

public class ChatClient implements ClientExports {

    String name; 
    public ChatClient(String name) throws RemoteException {
        System.out.println("Starting up client..."); 
        this.name = name; 
    }

    public void update(String message) {
        System.out.print(message); 
    }

    public static void main(String[] args) {
        try {

            ServerExports serverFarAway = 
                (ServerExports) Naming.lookup( "rmi://" + args[1] +
                                               ".cs.indiana.edu:" + 
                                               args[2] + "/Dirac");

            ChatClient thisGuyHere = new ChatClient(args[0]); 

            UnicastRemoteObject.exportObject(thisGuyHere); 

            serverFarAway.register(thisGuyHere); 
            serverFarAway.broadcast("***(" + thisGuyHere.name +
                                    ")*** has just connected.\n"); 

            thisGuyHere.talkAndRelayTo(serverFarAway); 
            
        } catch (Exception e) {
           System.out.println("Client: error in main..." + e); 
        }
    }


    public void talkAndRelayTo(ServerExports remoteServer) {

        try {

            InputStreamReader reader = new InputStreamReader(System.in); 
            BufferedReader br = new BufferedReader(reader); 
            String line = br.readLine(); 

            while (! line.equals("quit")) {

                System.out.println("You typed: " + line); 
                remoteServer.broadcast(name + "> " + line + "\n"); 
                line = br.readLine(); 

            }

        } catch (Exception e) {
            System.out.println("Client: error in talk..." + e); 
        }
    }
   

}
burrowww.cs.indiana.edu% cat ChatServer.java
import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 
import java.util.*; 

public class ChatServer 
    extends UnicastRemoteObject
    implements ServerExports 
{
    Vector chatters = new Vector(); 

    public ChatServer() throws RemoteException {
        System.out.println("Server being initialized..."); 
    }

    public void register(ClientExports remote) throws RemoteException {
        chatters.addElement(remote); 
    }

    public void broadcast(String message) throws RemoteException {
        for (int i = 0; i < chatters.size(); i++) {
            try {

                ((ClientExports)(chatters.elementAt(i))).update(message); 

            } catch(Exception e) {
                System.out.println("Client unavailable..."); 
            }
        }
    }

    public static void main(String[] args) {
        System.setSecurityManager(new RMISecurityManager()); 
        try {
            ChatServer pam = new ChatServer(); 

            Registry catalogue = 
                LocateRegistry.createRegistry(Integer.parseInt(args[0]));

            catalogue.bind("Dirac", pam); 

            System.out.println("Server is ready..."); 

        } catch (Exception e) {
            System.out.println("Server error: " + e + "..."); 
        }
    }
}
burrowww.cs.indiana.edu% javac *.java
burrowww.cs.indiana.edu% ls -ld *.java
-rw-r--r--   1 dgerman  faculty      1469 Nov 13 14:17 ChatClient.java
-rw-r--r--   1 dgerman  faculty      1138 Nov 13 16:08 ChatServer.java
-rw-r--r--   1 dgerman  faculty       135 Nov 13 16:07 ClientExports.java
-rw-r--r--   1 dgerman  faculty       208 Nov 13 16:08 ServerExports.java
burrowww.cs.indiana.edu% rmic ChatServer
burrowww.cs.indiana.edu% rmic ChatClient
burrowww.cs.indiana.edu% exit
burrowww.cs.indiana.edu% 
We'll discuss this in class.

We'll test it this way.


Last updated: Nov 13, 2001 by Adrian German for A348/A548