In this project you'll write code that interacts with a registration service. The function of the server is to keep track of nodes that participate in some distributed application. When a node comes up, it announces its presence by registering with the service, providing it with an IP address and port it can be contacted at. When a node goes down, it should unregister. Any node can ask the registration service for a list of currently registered nodes. The registration service is therefore a discovery mechanism -- a way for nodes to find each other.
This sounds simple enough, but there are quite a few details required for it to work robustly. Those are described next.
The code is this project is intended to be a utility for the next project. In Project 1 you build registration as a standalone application, but if you're not careful to cleanly separate support for the registration protocol from the implementation of the application, you'll end up having to do that in the next project.
Communication involving the registration service is always "request-response": one side sends a message to the other, the other responds, and that's the end of it. The possible exchanges are these:
Register registers an instance. The server returns a Registered response. Unregister removes a registration. The server responds with an ACK. Fetch requests information about registrations. The server responds with a FetchResponse message that contains a few randomly chosen registrations. Probe is simply a request for an ACK response. It is used to test if the other end is still there. Probe may be initiated by the client or by the server. All other exchanges are initiated only by the client.
Note: We have purposefully omitted any kind of error response, in an attempt to contain the work this project requires. If the server detects an error, it simply doesn't respond.
Here's a picture of the architecture we're headed. Examples of real services that might want to use your registration agent are audio servers, video servers, and peer-to-peer IM and VoIP clients. Rather than requiring each to implement code to deal with the registration server, you implement that code once and provide it as a library. In Project 1 there is no real client service; we'll implement one in Project 3, and use the registration agent to register it. You should try to design and implement your agent so that it's easy to use it with the Project 3 service. (We do write some UI code in Project 1 that can act as the service, so that we can test the agent, but we'll throw that code away.)
"Some service" wants to register the port its socket is bound to (q) with the registration service. In this project you simply make up some port number to register, since we're not actually implementing a client service at this time.
With that small issue ignored, the rest of the figure is accurate, even in this project. The registration agent itself has a distinct pair of datagram sockets that it uses when communicating with the registration service. The sockets are bound to consecutive ports (p and p+1, for some arbitrary p). One socket is used when the agent wants to initiate a request-response exchange with the registration service. The other is there for the registration service to use if it wants to initiate an exchange. Having separate sockets avoids confusions like: (a) the agent sends a Register to the registration service, and expects to receive an Registered message back; but (b) at the same time, the registration service sends a probe to the agent. If only a single port were used at the agent for both purposes, the agent would read a Probe when it expected a Registered, and get confused. (It's possible to make the one port approach work, but it's clumsy.)
The registration service requires that ports to which the agent's two sockets are bound be consecutive because the agent messages never explicitly give the agent's own port numbers. Instead, the registration service learns the sending port number (p) because it's in the UDP header of the messages it receives from the agent. It deduces the port number to which it should send Probes (p+1) because of the offline agreement that it should be one higher than the agent's sending port number.
Summarizing the basic operation, Register:
All registration messages begin with a common header consisting of the two-byte value 0xC461, a one-byte unsigned integer sequence number, and a one-byte message type. All multi-byte numeric values are in network order (big endian). Sequence numbers from a single sender form an increasing sequence, except that:
As well as providing the IP address and port at which the service can be contacted, the service provides two additional pieces of information. The "service data" is an arbitrary 32-bit data item the service wants to publish. It is communicated as an unsigned 32-bit integer in network byte order. The "service name" is a variable length string describing the service. The service name len field gives the length of the service name as an unsigned, 8-bit integer.
The lifetime field is an unsigned integer value indicating how long the registration service will keep the data it received in the Register message before deleting it. If the client service wants to maintain its registration, it must re-register before the lifetime expires. The lifetime is given in seconds. Note: your client must automatically re-register itself before the lifetime expires, without any necessary input from the user! Clients that re-register services that have been manually unregistered are not considered to be compliant with this spec.
The server tries to return all matching registrations, but imposes a maximum packet length for its response that may cause it to omit some registrations. The packet length limit ensures that the response fits in a single UDP packet, and, further, that there is at least a reasonable chance of delivery (based on Internet behavior observed in some simple experimentation).
Each returned entry looks like this:
Unregister expects an ACK response on success.
Service errors are things like responding with the wrong message type or a bad sequence number, or not at all (having crashed, for instance).
When network errors occur, you should try to overcome them. Service implementation errors will presumably just be repeated, so you can give up and report an error if you encounter one.
It's typically hard to debug the code intended to deal with network errors, because they occur infrequently. To help debug, the service instance deployed on cse461.cs.washington.edu (explained later) artificially drops some incoming packets. This is done probabilistically. The service simulates bursty errors. At any moment it is in one of two states, one with a low artificial drop rate and one with a very high rate. It transitions from one state to the other at random.
$ ./run <registration service host name> <service port>
Your client should accept commands from stdin that cause it to send messages to the registration service identified by the command line arguments, to read its responses the service sends, and to print appropriate messages about the result of the interaction. You must support the following commands. You may also support additional command formats that you find useful. (For example, the sample solution can use default arguments if required arguments are not provided.) Please document any additions in your README.TXT after your names/student numbers/e-mail addresses.
We aren't too picky about the format of your output, but you should try to make it clear to a TA reading it. (Feel free to imitate the format used in the sample solutions below.)
It can be useful when debugging your client code to have some idea what the server is receiving and sending, and what registrations it holds. For that reason, the registration service provides a web interface:
http://cse461.cs.washington.edu:8080/statusDisplays the current registration data.
http://cse461.cs.washington.edu:8080/logThe server produces a log of activity, including incoming packets shown as hex strings and as parsed messages. This page shows the most recently written (up to) 250 lines of the log.
A reasonable client protocol state machine looks like this, where "<request>" and "<response>" mean any request message and its anticipated response message.
Protocol questions: