![]() |
Distributed Systems Lab 2003 |
||||||||||||||||||||||||||||||||
|
![]() |
Lab 1: Multicast communicationGeneral ExplanationsRecall that the first lab example is also the qualification for the lab! Thus it is mandatory that you receive at least 50% of all points for this lab. See the Qualification and Grading sections for more details. We also recommend that you start early with this lab to avoid time problems before the deadlines. OverviewIf you have not yet read the overview description , do it now! The following instructions assume that you read and understood the high-level overview. In the first lab example, you will provide the underlying infrastructure to maintain a list of currently available peers. This example covers (among other topics) property files, multi-threading, UDP sockets, datagrams, multicast sockets, inner classes and object serialization (see the reading suggestions below). In the first task, you only have to customize the template property file provided in the resources directory. In the next task, you create a skeleton implementation of the ShareMeImpl class which will be gradually extended throughout this and subsequent lab examples. In the third step, a first thread for gracefully shutting down your peer is implemented. The thread listens for incoming shutdown messages on the specified port and only if the message has the correct format and contains the appropriate password, the server is shut down. After the initial setup tasks, a separate thread for receiving IAmAlive messages is introduced. This thread listens for multicast messages and processes the incoming datagram packets. Since IAmAlive messages are sent periodically, a list of currently available peers has to be maintained which is constantly kept up-to-date, i.e., expired entries have to be removed and new entries have to be added. The so-called host list with its separate garbage collector thread achieves this. Finally, you implement another thread to make yourself known to other peers, i.e., send IAmAlive messages to the multicast group. Your finished lab example maintains a list of all peers currently online and also announces your system to be available. Reading Suggestions
Deliverable (Submission)Execute the following command line (in a single line!) in the $HOME/src directory of your lab account. Recall that your submissions are only accepted if they are done from your lab account! tar cvfz - `find . -name "*.java"` | uuencode submission.tgz | mail -s 1 rnabgabe@dslab.tuwien.ac.at Note that the find command is surrounded by backticks (`) and NOT regular apostrophies (')! Task DescriptionsTask 1.1: Create a property file for your ShareMe implementationCreate your customized ShareMe property file using the provided template in the resources directory. The property file is used as configuration information for your ShareMe peer - make sure that you really use the values specified in the property file and do not hardcode the values in your sources (we use different property files for testing and grading purposes!). Copy the provided template and create a separate property file for every ShareMe peer you intend to start. In the lab environment, you probably only need a single peer; if you work at home, several peers are necessary to support reasonable testing of your implementation. More information about the entries and the corresponding values in the property file can be found in the introduction to the lab examples . Additionally, you can assume that port numbers are positive, i.e., we won't test your implementation with negative port numbers! Task 1.2: Create a skeleton for your ShareMe implementationCreate a class called at.ac.tuwien.infosys.rnue.implementation.ShareMeImpl . This class must have a constructor taking a java.util.Properties object as the first argument. The constructor may throw the at.ac.tuwien.infosys.rnue.helpers.ShareMeException exception in case of an error (for instance if no properties are specified). If you use one of our classes to start your implementation and get a property object in the constructor, you can assume that it contains all properties specified in your property file. In the constructor, get the IP address of your local machine using the java.net.InetAddress class and store the IP address in the properties using IConstants.RMI_REGISTRY_HOST as key. Recall that the IConstants interface provides many constant values such as keys for the property file, socket timeouts or maximum thresholds. This property will be important to make yourself known to the other peers (hosts). The class must also implement the at.ac.tuwien.infosys.rnue.interfaces.IShareMe interface. Implement the methods with the following functionality:
You can run a first test of your implementation by compiling your class (i.e., running ant lab1 ) and running the main program at.ac.tuwien.infosys.rnue.helpers.ShareMeMain supplied in the lab package (i.e., running ant run1 ). The ShareMeMain is already included in the ShareMe library - you don't have to implement it yourself! Use your property file as command line parameter when you invoke the main program. java at.ac.tuwien.infosys.rnue.helpers.ShareMeMain {property file}If everything works as expected, you should see the text which is printed in the start() method on the console. Then the program terminates. Task 1.3: Implement a "shutdown listener" threadSince your ShareMe peer also has a server part which will (later on) be running permanently, we need a way to terminate the server. We do not want to use CTRL+C to terminate the server. Hence, your server has to listen on a given port for a UDP packet that instructs the server to shut down. Since the server must not block while listening, we use a thread (the shutdown listener) for this purpose. We have provided a small shutdown program ( at.ac.tuwien.infosys.rnue.helpers.StopShareMe ) which takes the host and port of your server as parameters and prompts for the password defined in the property file. The shutdown program sends a UDP packet to the given host and port. At this destination, your program has to listen for such packets. The shutdown listener must be implemented in the at.ac.tuwien.infosys.rnue.implementation.multicast.ShutdownListener class and has to implement the Runnable interface. The constructor takes three parameters (in the following order) and may throw the at.ac.tuwien.infosys.rnue.helpers.ShareMeException :
In the run() method of the Runnable interface, you have to create a new DatagramSocket with the given port and listen for incoming DatagramPackets . The payload of the packet is limited to IConstants.MAX_UDP_PACKET_LENGTH bytes and has to be converted to a java.lang.String . Hint: Use the String(byte[] bytes, int offset, int length) constructor to convert the payload of the datagram packet to a String object. This avoids leading or trailing garbage data. Use the datagram's getData() , getLength() and getOffset() methods to gather the required information. Do not use trim() on any part of the content! You have to accept and discard all packets until the packet's payload consists of a string of the following format: IConstants.EXIT_FLAG followed by a blank (" ") and the shutdown password as defined in the properties. If you receive malformed packets or an exception occurs, print an error message to the console. Once you receive this special packet, call the stop() method of the IShareMe instance passed as a parameter to the constructor. Hint: It is important that you create a new datagram packet every time you receive one. Otherwise, the packet's length and content is not correctly determined when the next packet arrives. When you finish implementing the shutdown thread, you can instantiate it in your ShareMeImpl class. At the very end of the start() method, instantiate the ShutdownListener class, create a new java.lang.Thread with it and start the thread. Use the IConstants.SHUTDOWN_PORT and the IConstants.SHUTDOWN_PASSWORD properties specified in the properties file to instantiate the shutdown listener. Now, again compile and start the test main program as described in the previous task. Now your program should block and wait for the shutdown packet to arrive. With the at.ac.tuwien.infosys.rnue.helpers.StopShareMe helper program, you can send the appropriate shutdown packet (after entering the password) to your server. If everything works, the server will terminate. The command line of the shutdown program looks as follows (if you do not specify the host, the local host will be used): java at.ac.tuwien.infosys.rnue.helpers.StopShareMe -p {port} [-h {host}] Task 1.4: Receive "IsAlive" messages from other peersSo far your ShareMe peer does nothing but wait for shutdown packets. In this task, we add the capability to listen for 'IAmAlive' multicast messages from other peers. As was sketched in the overview , every peer periodically sends such a multicast message to a specified address and port (the multicast address/port) to announce that it is online. A list of currently available hosts has to be maintained (see Task 1.5); in this task we concentrate on receiving the appropriate messages. You have to create the at.ac.tuwien.infosys.rnue.implementation.multicast.IsAliveReceiver class which implements at.ac.tuwien.infosys.rnue.interfaces.IIsAliveReceiver . This interface extends Runnable and provides a stop() method. Hence, the IsAliveReceiver again is a separate thread. Start with the implementation of the constructor which has to take a properties object (from your ShareMeImpl ) and a at.ac.tuwien.infosys.rnue.interfaces.IHostList object as parameters (in this order!) and may throw the at.ac.tuwien.infosys.rnue.helpers.ShareMeException exception. The class implementing the IHostList interface will be created in the next task to store the list of currently available peers. In the constructor get the multicast address and port from the properties using the keys defined in at.ac.tuwien.infosys.rnue.interfaces.IConstants (i.e., IConstants.MULTICAST_ADDRESS and IConstants.MULTICAST_PORT ). Further create a new MulticastSocket using the above multicast port and set the socket timeout to IConstants.IS_ALIVE_RECEIVER_TIMEOUT (see the java.net.MulticastSocket API for details). Hint: To set the timeout for the socket, use the setSoTimeout() method! setTimeToLive() is NOT the same! In the run() method, make the socket join the group with the given multicast address (using the socket's joinGroup() method). As in the shutdown listener, you have to create a datagram packet with a buffer of size IConstants.MAX_ISALIVE_PACKET_LENGTH . In a loop which is controlled by a boolean flag, keep listening for incoming packets. The boolean flag of the loop will be modified by the stop() method of the IIsAliveReceiver to indicate that the thread shall stop. Make sure you catch and ignore the InterruptedIOException which occurs when the sockets timeout expires. This is necessary to allow the thread to check once in a while whether the boolean flag controlling the loop was changed! In contrast to the shutdown listener, the 'IAmAlive' packets do not contain a simple string but a serialized Java object. If you are not familiar with Java object serialization, check the tutorial now. Get the byte data from the packet and use the ByteArrayInputStream and ObjectInputStream to retrieve the included object. Then use the instanceof operator to check whether the object really is of type at.ac.tuwien.infosys.rnue.interfaces.IHostInfoMessage . If not, ignore the message. If you correctly received an IHostInfoMessage object, print the message's sender to Sytem.out to later on check whether your implementation works. After the loop just tell the multicast socket to leave the multicast group it joined. Finally, implement the stop() method, i.e., set the boolean flag controlling the loop to false . Integrate your IsAliveReceiver thread with the ShareMeImpl by creating a new instance of the class in the start() method. For the moment, use a null parameter for the host list; we'll change this once we implement the host list. Then, create a new thread with your instance and start the thread. In the stop() method of the ShareMeImpl class add a call to the stop() method of the IIsAliveReceiver instance which in turn will reset the boolean flag controlling the loop and quit. You can again test your implementation by repeating the compilation and execution steps as described in the previous task (i.e., compile everything, start the ShareMe implementation and use the helper program to terminate it). You should see a list of currently available hosts being printed to the console. In the lab environment, at least the DSG peers will exist. If you are working at home, you should see nothing since nobody is sending IAmAlive messages at the moment. Task 1.5: Store known hosts in a garbage collected repositoryIn this task, we implement the repository which stores all hosts currently being online (including your own host). In principle, it is a simple java.util.Hashtable which stores this information. The information about a host is encapsulated in IHostInfo objects which contain the actual IHostInfoMessage and a timestamp when the message arrived. Since we will later use RMI to talk to the remote peers, the key for the entries in the hashtable is calculated as shown below (of course, the key is built without the curly brackets; e.g., labsrv01.dslab.tuwien.ac.at:4711 ). Among other information, the RMI registry host and registry port are stored in the IHostInfoMessage object. {RMIRegistryHost}:{RMIRegistryPort} The first step here is to implement the class at.ac.tuwien.infosys.rnue.implementation.multicast.HostListImpl which implements the IHostList interface and has a constructor with no parameters. Implement the methods of the IHostList using a java.util.Hashtable as underlying data structure (with the key and values as explained before). Leave the implementation of the stopGarbageCollector() method empty for now. When you finish implementing the interface, we have a fully functional repository to store IHostInfo objects. Now recall that the peers periodically send 'IAmAlive' messages. Thus an entry in the host list is only valid for a certain amount of time or as long as it is refreshed by newly arriving 'IAmAlive' messages. As a consequence, we need a garbage collector for our host list which periodically removes expired entries from the host list. Since it might happen (we are using UDP!) that we once in a while miss an 'IAmAlive' message from another peer, the garbage collector runs in longer intervals than the interval 'IAmAlive' messages are sent. This means that we only remove a host from the list if we are missing several 'IAmAlive' messages! From the description above it is already clear that the garbage collector periodically performs its cleanup task. We use another thread (the garbage collector thread) to do that. Implement the GarbageCollector garbage collector class as inner class of your host list implementation. The class has to implement Runnable . In the constructor it gets the host list instance and in the GarbageCollector.run() method we have to perform the cleanup task and then wait for a given amount of time. This is typically done using a loop (again controlled by a boolean flag which indicates whether the garbage collector should continue its operation). In the loop, perform the cleanup task and then call wait() in a synchronized block as shown below: synchronized(this) { try { ((java.lang.Object)this).wait(IConstants.GC_INTERVAL); } catch (InterruptedException ie) { // do nothing; this was on purpose, i.e., we waited long enough } } The cleanup task itself is to get the current time using the System class and iterating through all values in the host list, extracting the arrival time of the message and comparing whether it is already expired (i.e., stored longer than IConstants.LIFETIME_OF_HOSTINFOS ). Collect all entries which are expired and remove them from the list. Make sure that all methods of the host list operate on the underlying hash table in an atomic way. This is principally automatically achieved by relying on the sychronized nature of the hash table itself. In some situations, however, your implementation has to take special care of synchronisation issues. Such a situation arises if one of your methods requires more than a single call to the hashtable to implement an atomic action. Imagine, as an example, that you implement a method to calculate a hash sum of all the entries in the hash table. You have to make sure that nobody (i.e., no other thread) inserts or removes entries as long as your calculation is not finished. As a consequence, you have to add a synchronization construct to make sure that no other thread can access the hash table as long as your code is doing its calculation. // make the following set of operations // atomic on the shared resource synchronized(monitor_object) { ... execute operations on shared resource here ... } In the implementation of the host list garbage collector you come across such a situation when you go over all entries in the hash table, collect the outdated ones and remove them. This operation also has to be atomic and thus requires the above synchronisation statement! To complete the garbage collector thread, add a stop() method which resets the boolean flag controlling the loop and calls this.notify() to interrupt the waiting thread. This again should be done in a synchronized block as shown above. Don't forget to call the stop() method in the host list's stopGarbageCollector() method! The only remaining part to finish the implementation of the host list is to create a new instance of the garbage collector in the host list's constructor, create a new thread with it and start the thread. Now we are almost done. We merely have to integrate the host list implementation with the already existing infrastructure as follows:
Task 1.6: Make yourself known to othersSo now we know who is available in the network. But we don't tell anyone yet that we are also online! Thus in this final task we add an IsAliveSender thread which periodically sends multicast IAmAlive messages to the other peers. Since you just saw (in the garbage collector thread) how to make a thread perform a task periodically using the wait() method, we now introduce an alternative solution to this problem. Again, the IsAliveSender has to do something periodically; namely send IAmAlive messages. However, this time we use a TimerTask to achieve this behavior. Create a new class at.ac.tuwien.infosys.rnue.implementation.multicast.IsAliveSender which extends TimerTask and implements IIsAliveSender . The constructor gets the properties as parameter, can throw a ShareMeException , and performs the following tasks:
Creating the datagram packet in the constructor makes sense since we keep sending the same packet all the time! In the next step, we implement the run() method. This is extremely simple due to the fact that we use the TimerTask approach. Just call the send() method of the multicast socket and send the previously created datagram packet. The timing is done for us - we'll see how to configure it in a moment. As in all our threads, we also add a stop() method. In this case, the stop() method only tells the multicast socket to leave the group (if you previously joined it; see above) and calls this.cancel() which tells the timer task that it should stop. Finally, we integrate the sender with the ShareMeImpl.start() method by creating new Timer and IIsAliveSender objects and associating the object implementing IIsAliveSender with the timer (using Timer.scheduleAtFixedRate() ). Hint: Note that you cannot use the interface IIsAliveSender itself as parameter for the timer. This is because the interface is not a subclass of TimerTask . As a consequence, you have to pass a reference to your implementation of the interface as parameter to the timer (i.e., IsAliveSender) and not a reference to the actual interface (i.e., IIsAliveSender)! The timing of the timer thread is determined by IConstants.IS_ALIVE_INTERVAL . In the ShareMeImpl.stop() method, don't forget to add a call to the IsAliveSender's stop() method and also call cancel() on the timer object! When you finished all tasks of the first lab, your ShareMe system should be aware of other available peers and announce its own availability to the others. You can easily test the correct functioning of your implementation. First, add some System.out.println() statements to your code to monitor the contents of your host list, i.e., check if new peers are added and unavailable peers are removed correctly. Then start and stop several instances of your implementation (using different property files, of course) and monitor the output of the various running instances. Obviously, you have to extend the build.xml file or execute your instances from the command line to start them with different property files. |
![]() |
||||||||||||||||||||||||||||||
![]() |
![]() |
||||||||||||||||||||||||||||||||
Powered by MyXML |
Last update on:
2003-03-13
© 2001 Distributed Systems Group |