Carnegie Mellon University June 2010 95-702 Organizational Communications and Distributed Object Technologies Project 3 Due by 11:59 PM on Tuesday, June 22, 2010 Principles ========== This project naturally leads to discussions concerning non-functional characteristics of distributed systems such as protocol reliability via positive acknowledgement with retransmission, unreliable networks, interoperability, marshaling and external data representation, naming, separation of concerns and design patterns. Review ====== In Project 1 we worked with J2EE servlets and Java Server Pages using the Glassfish web container. In Project 2 we worked with J2EE servlets and web services using Glassfish and the web service support provided by JAX-WS 2.0. In this project we will be working at a lower level. That is, we will not have the Glassfish or the JAX-WS runtimes to rely on. You may, however, continue to use Netbeans for most of this work. For the Android piece, you will use Eclipse. TCP, UDP and Distributed Objects ================================ This project has three parts: The first part exposes the student to both TCP and UDP sockets. In this part we are interested in exploring issues associated with protocol reliability over unreliable networks. External representation and marshaling is also explored. The second part exposes the student to issues associated with remote method invocation. Code is provided that acts as sort of scaffolding upon which the student builds a more significant application. The scaffolding includes a client side proxy and server side skeleton. Using that code as a model to work from, the student writes a simple registry and an object server and client. The program uses sockets but is careful to illustrate separation of concerns and the use of the proxy design pattern. Ideas from the Colouris text, such as remote object references and request reply messages, must be implemented by the student. The third part illustrates the ubiquity and interoperability of UDP. The student is asked to build an Android application that interacts with the UDP server that was written in Part 1. Discussion ========== In the first part of this homework you will work with both UDP and TCP over IP. UDP is a simpler protocol than TCP and therefore usually requires more work from the application programmer. TCP presents the application programmer with a stream abstraction and is busier below the scenes. TCP, unlike UDP, tries its best to make sure packets are delivered to the recipient. The underlying communication systems that we are using may, on occasion, drop packets. So, how can TCP provide for reliable delivery of packets? TCP uses the fundamental principal of "positive acknowledgement with retransmission". A simplified example of "positive acknowledgement with retransmission" (with no packets lost) looks like the following. These notes are adapted from "Internetworking with TCP/IP, Volume I: Principles, Protocols and Architecture" by Douglas E. Comer Sender Service ===================================================== Send packet 1 Start timer Receive packet 1 Send Ack for packet 1 Receive ack 1 Cancel timer Send packet 2 Start timer Receive packet 2 Send Ack for packet 2 Receive Ack 2 Cancel timer Here is an example where the first packet is lost. Sender Service ===================================================== Send packet 1 Start timer Packet lost Time expires Send packet 1 Start timer Receive packet 1 Send Ack for packet 1 Receive Ack 1 Cancel timer Send packet 2 Start timer Receive packet 2 Send Ack for packet 2 Receive Ack 2 Cancel timer Here is an example where the first ack is lost. Sender Service ===================================================== Send packet 1 Start timer Receive packet 1 Send ack 1 Ack 1 lost Time expires Send packet 1 Start timer Receive packet 1 a second time Send Ack for packet 1 Receive Ack 1 Cancel timer Send packet 2 Start timer Receive packet 2 Send Ack for packet 2 Receive Ack 2 Cancel timer The acknowledgement may be replaced with the result of the service. Here is another example. Sender Service ===================================================== Send packet 1 Start timer Receive packet 1 Send response Response Lost Time expires Send packet 1 Start timer Receive packet 1 a second time Send response again Receive response Cancel timer Send packet 2 Start timer Receive packet 2 Send response for packet 2 Receive response Cancel timer Part I. UDP and TCP From Chapter 4 of the Coulouris text ======================================================== (1) On pages 138 and 139 of the Coulouris text, two short programs are presented. Make modifications to the UDPClient program and the UDPServer program so that the client may ask the server to perform simple integer arithmetic. You need to implement addition, subtraction, multiplication and division of primitive integers. You may assume that you have a well behaved user and all input is correct. You may also assume that the user uses spaces to separate the command line arguments. The execution of the client program will look like the following: java UDPClient 100 + 234 Reply: 334 In Netbeans, command line arguments can be set by choosing Run/Set Project Configuration/Customize. UDPClient.java and UDPServer.java will be placed in a project called BasicUDPProject and submitted to Blackboard. (2) Using the same UDP server that you wrote in (1), write a new UDP client named UDPClientWithProxy.java that has a main routine that computes and displays the sum of the integers 1+2+3+...+100. The main routine of the client must be very clean and contain no socket level programming. All of the socket work will be done within a single method with the following signature: public static int add(int x,int y); The main routine will make 100 calls on the add method. The add method, however, will not perform any addition. Instead, it will send a UDP message to the server and receive the server's response. It will return that response as a simple int. This is a proxy design. UDPClientWithProxy.java will be placed in the same project (BasicUDPProject) as question 1. (3) Build a new project called ReliableUDPProject. Modify the UDPServer and create a new java class called UDPServerThatIgnoresYou.java. Write the new server so that it randomly ignores 70% of requests. In other words, the new UDPServerThatIgnoresYou will contain code close to this: // rnd is an object of the Random class aSocket.receive(request); if(rnd.nextInt(10) < 7) { System.out.println("Got request " + new String(request.getData())+ " but ignoring it."); continue; } else { System.out.println("Got request" + new String(request.getData())); System.out.println("And making a reply"); } Create a new client called UDPClientWithReliability.java. This new client is a modification of UDPClientWithProxy. After a request, it waits only 2 seconds for a reply. If the reply does not arrive after two seconds, the client tries again. It never gives up. The UDP receive will look something like this. aSocket.setSoTimeout(2000); aSocket.receive(reply); See the above discussion on "positive acknowledgement with retransmission". UDPClientWithReliability.java will have a main routine that computes and displays the sum of the integers 1+2+3+...+100. The main routine of the client must be very clean and contain no socket level programming. All of the socket work (and retry code) will be done within a single method with the following signature: public static int add(int x,int y); (4) On pages 142 and 143 of the Coulouris text, two short programs are presented. Make modifications to the TCPClient program and the TCPServer program so that the client may ask the server to perform simple integer arithmetic. You need to implement addition, subtraction, multiplication and division of primitive integers. You may assume that you have a well behaved user and all input is correct. You may also assume that the user uses spaces to separate the command line arguments. The execution of the client program will look like the following: java TCPClient 100 + 234 Reply: 334 It is required that a proxy design be used. All of the socket level programming needs to be isolated. Name this project BasicTCPProject. It will contain the files TCPServer.java and TCPClient.java. Part II. Low Level RMI ====================== See the programs at the end of this document. These programs illustrate low level remote method invocation (RMI). That is, we are not yet working with Java RMI. We are implementing a simple RMI system using TCP sockets. (5) Your first task is to get these programs running and to study them closely. The terms "skeleton" and "stub" are used and you should understand exactly what kind of objects these terms refer to. Get the programs working in two separate Netbeans projects. Two separate projects works best because it clearly separates the client code from the server code. The interface file (Person.java) must be placed on the server side as well as the client side. When the server and client are run, Netbeans will present two different console windows - one for the client and one for the server. There is a box on the console window that may be used to stop the server process. Name these projects LowLevelRMIClientProject and LowLevelRMIServerProject. Submit these to Blackboard. Using a Registry ================ The following questions ask you to build a distributed object server, a registry and a client. The registry is located at port 9090. / \ lookup / \bind / \ The client makes a \ lookup call The object server at port 9000 on the registry -----rmi----- makes a bind call on the registry and then calls and then services calls methods on the remote on an object. object. (6) Create three new Netbeans projects named ClientProject, ObjectServerProject, and RegistryServerProject. a. Write a new Java class called RemoteObjectReference that implements Serializable. This class will encapsulate the private fields found in Figure 4.13, on page 154, of the Coulouris text. This class will have getter and setter methods for each field and a small main routine used as a test driver. The "interface of remote object" field will be defined as a String object. The IPAddress field is an array of 4 bytes. The other two fields are simple integers. This class will be present in ClientProject, ObjectServerProject and the RegistryServerProject projects. b. Write a new Java class called RequestReplyMessage that implements Serializable. This class will encapsulate the private fields found in Figure 4.16, on page 157, of the Coulouris text. Note that a RequestReplyMessage object has a RemoteObjectRef member. This must be the same class as defined in the previous step. Setter and getter methods need to be defined. Note that a RemoteObjectReference object contains enough information to specify a remote object. The RequestReplyMessage contains, in addition, information on the method that will be invoked (or was invoked) on that object as well as an array of bytes specifying the arguments to or return values from the method. The RequestReplyMessage class will be present on the client, object server and registry server. c. Write the following Java classes for the client project: * PersonClient.java PersonClient creates a Binder_Stub object so that it may make lookup calls on the registry. It will call lookup("Mike") and receive a RemoteObjectReference object from the registry. PersonClient will then create a Person_Stub object with the RemoteObjectReference as a parameter to the Person_Stub constructor. It will then make calls on the stub to retrieve the name and age in the remote object. * Binder_Stub.java The client needs to speak to the registry. This class implements the Binder interface and contains bind and lookup methods. The client only makes use of the lookup method. * Binder.java This interface defines the lookup method as taking a string argument and returning a RemoteObjectReference value. It also defines the bind method that takes a string and a RemoteObjectReference as input. Its return type is void. * Person_Stub.java The client needs to speak with the server. This class implements the Person interface and contains getAge and getName methods. When making a call on the server, it creates a RequestReplyMessage from the information found in the RemoteObjectReference and sends this message to a TCP socket. When receiving a reply, it reads a RequestReplyMessage from the socket, extracts bytes, and returns to the caller either the name or age. * Person.java This is the same interface as shown below. * RemoteObjectReference.java and RequestReplyMessage.java. See above for a description of these classes. d. Write the following Java classes for the registry project. * Binder_Servant.java This servant object holds a mapping of of names to RemoteObjectReference objects. This class implements the Binder interface. A Java TreeMap will be used to hold the mappings. * Binder.java See above for a description. * Binder_Skeleton.java This skeleton is used to communicate with the server and the client. The serve method of this class is written like the serve method in Person_Skeleton shown below. On bind calls, it reads String objects and remote object reference objects from sockets and makes calls on its Binder_Servant. On calls to lookup, it writes a RemoteObjectReference object to the socket. Nothing is written to the socket on a call to bind. * BinderServer.java This class starts up the registry. It creates a Binder_Servant and passes that servant to its new Binder_Skeleton and calls serve. * RemoteObjectReference.java This class is described above. e. Write the following Java classes for the object server: * Binder_Stub.java See above. * Binder.java See above. * Person_Servant.java This class implements Person and is shown below. * Person_Skeleton.java This class has a server method that reads RequestReplyMessages and writes RequestReplyMessages. In between the reading and writing it makes a call on the Person_Servant object. * Person.java See below. * PersonServer.java This class creates a Person_Servant object and a RemoteObjectReference. It uses the Binder_Stub to make a bind call on the registry. It creates a Person_Skeleton object and asks it to serve. * RemoteObjectReference.java See above. * RequestReplyMessage.java See above. You need not communicate with the registry using a request reply message. On a call to bind, simply call with two objects (a string and a remote object reference.) Part III. Bonus Section ======================= (7) Write an Android application that uses UDP to interact with the server in Part 1 question 1. Call this application AndroidUsingUDPProject. This will be an interactive application with a button labelled "UDP Send" and a text box that is able to collect a simple arithmetic expression. The result of the computation will be displayed on the phone. Summary ======= Part I. ======= (1) BasicUDPProject 10 Points UDPClient.java UDPServer.java Screenshot showing solution (2) Also in BasicUDPProject 10 Points UDPClientWithProxy.java Screenshot showing solution (3) ReliableUDPProject 20 Points UDPServerThatIgnoresYou.java UDPClientWithReliability.java Screenshot showing solution (4) BasicTCPProject 10 Points TCPServer.java TCPClient.java Screenshot showing solution Part II. ======== (5) LowLevelRMIClientProject 10 Points Person.java Person_Stub.java PersonClient.java Screenshot showing solution LowLevelRMIServerProject Person.java Person_Skeleton.java Person_Servant.java PersonServer.java Screenshot showing solution (6) ClientProject 30 Points ObjectServerProject RegistryServerProject Screenshot showing solution Part III. Bonus Section ======================= (7) AndroidUsingUDPProject 10 Points Screenshot showing solution Paper ===== Describe protocol reliability with respect to Part I, question 3. Describe the interoperability of Part II. Describe marshaling and external data representation with respect to Part I, question 1. Describe naming and part II. Describe separation of concerns and the proxy design. ================================================================= ================================================================= // file: Person.java on both the client and server side public interface Person { public int getAge() throws Exception; public String getName() throws Exception; } ================================================================== ================================================================== // file: Person_Stub.java found only on the client side import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; public class Person_Stub implements Person { Socket socket; ObjectOutputStream o; ObjectInputStream i; public Person_Stub() throws Exception { } public int getAge() throws Exception { socket = new Socket("localhost",9000); o = new ObjectOutputStream(socket.getOutputStream()); o.writeObject("age"); o.flush(); i = new ObjectInputStream(socket.getInputStream()); int ret = i.readInt(); socket.close(); return ret; } public String getName() throws Exception { socket = new Socket("localhost",9000); o = new ObjectOutputStream(socket.getOutputStream()); o.writeObject("name"); o.flush(); i = new ObjectInputStream(socket.getInputStream()); String ret = (String)(i.readObject()); socket.close(); return (String)ret; } } ======================================================== ======================================================== // file: PersonClient.java exists only on the client side public class PersonClient { public static void main(String args[]) { try { Person p = new Person_Stub(); int age = p.getAge(); System.out.println("Age = " + age); String name = p.getName(); System.out.println(name + " is " + age + " years old"); } catch(Exception t) { t.printStackTrace(); System.exit(0); } } } ============================================================ ============================================================ // file: Person_Skeleton.java exists only on the server side import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; import java.net.ServerSocket; public class Person_Skeleton { Person myServer; public Person_Skeleton(Person s) { myServer = s; } public void serve() { try { ServerSocket s = new ServerSocket(9000); while(true) { Socket socket = s.accept(); ObjectInputStream i = new ObjectInputStream(socket.getInputStream()); String method = (String)i.readObject(); if(method.equals("age")) { int a = myServer.getAge(); ObjectOutputStream o = new ObjectOutputStream(socket.getOutputStream()); o.writeInt(a); o.flush(); } else if(method.equals("name")) { String n = myServer.getName(); ObjectOutputStream o = new ObjectOutputStream(socket.getOutputStream()); o.writeObject(n); o.flush(); } } } catch(Exception t) { System.out.println("Error " + t); System.exit(0); } } } ============================================================== ============================================================== // file: Person_Servant.java exists only on the server side public class Person_Servant implements Person { int age; String name; public Person_Servant(String n, int a) { name = n; age = a; } public int getAge() { return age; } public String getName() { return name; } } // file: PersonServer.java exists only on the server side public class PersonServer { public static void main(String args[]) { Person p = new Person_Servant("Mike",23); Person_Skeleton ps = new Person_Skeleton(p); ps.serve(); } } ========================================================== ==========================================================