0% found this document useful (0 votes)
23 views

Assignment 7 2020 Problem

The objective is to build a reliable communication layer over unreliable UDP links. This will be done by implementing reliable send and receive APIs over UDP sockets using message IDs, buffers, and timers to handle retransmissions. The APIs will provide exactly-once reliable delivery of messages although ordering is not guaranteed.

Uploaded by

shivam sourav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views

Assignment 7 2020 Problem

The objective is to build a reliable communication layer over unreliable UDP links. This will be done by implementing reliable send and receive APIs over UDP sockets using message IDs, buffers, and timers to handle retransmissions. The APIs will provide exactly-once reliable delivery of messages although ordering is not guaranteed.

Uploaded by

shivam sourav
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

CS 39006: Assignment 7

Reliable Communication over Unreliable Links


Date: March 05, 2020
Due: March 19, 2020

Objective:

The objective of this assignment is to build support for reliable communication over an
unreliable link. The unreliable link will be implemented with a UDP socket. You will have
to give APIs to the user that will do reliable send/receive over this unreliable link by
appropriate implementation of the APIs.

Problem Statement:
In this assignment, you will be building support for reliable communication over an
unreliable link. In particular, you will build a message-oriented, reliable, exactly-once
delivery communication layer (you can think of it as TCP without byte-orientation and
in-order delivery, or UDP with reliable delivery). Message ordering is not needed, so
messages with higher ids can be delivered earlier.

You have been introduced to the function calls ​socket,​ ​bind​, ​sendto​, and ​recvfrom to
work with UDP sockets. Assume that the TCP sockets are not there. We know that UDP
sockets are not reliable, meaning that there is no guarantee that a message sent using a
UDP socket will reach the receiver. We want to implement our own socket type, called
MRP (​My Reliable Protocol​) socket, that will guarantee that any message sent using a
MRP socket is always delivered to the receiver exactly once. However, unlike TCP
sockets, MRP sockets may not guarantee in-order delivery of messages. Thus
messages may be delivered out of order (later message delivered earlier). However, a
message should be delivered to the user exactly once.

To implement each MRP socket, we use the following:

1. One UDP socket through which all actual communication happens.


2. One alarm signal handler (discussed later). Signal handler handles all the
messages received from the UDP socket and all timeout and retransmissions
needed for ensuring reliability.
3. At least the following three data structures associated with it (you can have
others of your own if you need them): ​send buffer, receive buffer​,
unacknowledged-message table,​ and ​received-message-id table​. The
sender buffer contains the application messages that need to be sent. The
receive buffer contains the application messages that are received. This will
contain each message sent exactly once before it is read by the user. The
unacknowledged-message table contains the details of all messages that have
been sent but not yet acknowledged by the receiver, along with the last sending
time of the message. This will be used to decide which messages need to be
retransmitted if no acknowledgement is received. The ​received-message-id
table contains all distinct message ids that are received in the socket so far. This
will be used to detect duplicate messages.
You can assume a maximum size of 100 bytes for each message and maximum
table sizes of 100 messages for each table. The tables should be ​dynamically
created when the socket is opened and ​freed when the socket is closed​.
The table pointers (or pointers to any other data structure you may need) can be
kept as global variables, so that they will be accessible from all API functions
directly.

The broad flow on ​send​ and r


​ eceive​ of messages is as follows:

Sending an application message for the first time: The user calls an API function
r_sendto() to send its message. The send API adds a unique id (use a counter starting
from 0) to the application message and adds it to the ​send buffer ​ with its id and IP-port.

Signal is a mechanism to interrupt a running process. Whenever a process receives a


signal, it runs the routine (C function) associated with the signal. There are about 64
different signals available in the unix system. In this assignment, you have to use a
SIGALRM signal. The SIGALRM signal is a mechanism to set an alarm for the process.
There are two mechanisms to set alarm 1) ​alarm() system call, 2) ​setitimer() system
call. You have to use ​setitimer() as it provides signals in a regular interval.
setitimer() has three different modes of setting up an alarm. Each mode sends out
different signals. We want you to use ​ITIMER_REAL mode which produces a SIGALRM
signal for the process. For more details about setitimer, open up ​man 2 setitimer.

Handling message received (application and ACK) from the socket and
Retransmission of messages: The signal handler will be called in regular intervals.
Whenever the handler gets invoked, it needs to do the following tasks one-by-one:

● It first calls the function ​HandleReceive() :- Here the program will try to receive
the next message from the udp socket by calling ​recvfrom() function with flag
MSG_DONTWAIT. MSG_DONTWAIT makes the ​recvfrom() call non-blocking,
so the program will not wait if there is no message available. The
HandleReceive() function calls ​recvfrom() function till there is some message
available and performs actions accordingly. Once a message is received by this
function, it does the following:
○ Check if it is an application message or an ACK message.
○ If it is an application message, call a function ​HandleAppMsgRecv()​,
which does the following:
■ check the ​received-message-id table if the id has already been
received (duplicate message). If it is a duplicate message, the
message is dropped, but an ACK is still sent. If it is not a duplicate
message, the message is added to the ​receive buffer (without the
id) including source IP-port, and an ACK is sent. The function
returns after that.
○ If it is an ACK message, call a function ​HandleACKMsgRecv(),​ which
does the following:
■ If the message is found in the ​unacknowledged-message table​,
it is removed from the table. If not (duplicate ACK), it is ignored.
The function returns after that.
After handling the message, the ​HandleReceive()​ function returns.

● Once ​HandleReceive() ​finishes, the signal handler calls the function


HandleRetransmit() ​:- This function handles any ​retransmission of
application messages as needed due to message loss. ​The function scans the
unacknowledged-message table to see if any of the messages’ timeout period
(T) is over (from the difference between the time in the table entry for a message
and the current time). If yes, it retransmits that message and resets the time in
that entry of the table to the new sending time. If not, no action is taken. This is
repeated for all the messages in the table. At the end of scanning all the
messages, the function returns.

● Once ​HandleRetransmit​() finishes, the signal handler calls the function


HandleTransmit() :- It first checks if there is an empty space in the
unacknowledged-message table.​ It returns without doing anything if there is no
space available. Otherwise, it iterates over the ​send buffer until send buffer is
empty or ​unacknowledged-message table ​is full and do the followings:
○ It picks the message with the lowest id from the ​send buffer and tries to
send it immediately by calling the ​sendto() system call with
MSG_DONTWAIT flag. If the ​sendto() call executes successfully, it
removes the message from the ​send buffer and puts it to the
unacknowledged-message table with the current timestamp, the
message id and the IP-port.
Once ​HandleTransmit() finishes, it returns, and the process comes out of the
signal handling. Kindly note that you need to make the entire signal handling part
fast. So, you should not use any blocking function call.

Receiving a message by the user: The user calls an API function ​r_recvfrom(),​
which either finds a message in the receive buffer or not. If there is a message in the
receive buffer, the first message is removed and given to the user, and the function
returns the no. of bytes in the message returned. If there is no message in the receive
buffer, the user process is (spin) blocked. The function will return when there is a
message in the receive buffer (if a message comes, it will be put in the buffer by the
signal handler).

You can assume there will be exactly one MRP socket created by a process for send
and receive, so only one set of the variables are needed.

You will be implementing an API with a set of function calls ​r_socket()​, ​r_bind()​,
r_sendto(),​ ​r_recvfrom(),​ and ​r_close() ​that implement MRP sockets. ​The
parameters to these functions and their return values are exactly the same as the
corresponding functions of the UDP socket, ​except for r ​ _socket.​ The functions will
be implemented as a static library. Any user wishing to use MRP sockets will write a
C/C++ program that will call these functions in the same sequence as when using UDP
sockets. The library will be linked with the user program during compilation.

A brief description of the functions is given below. All calls should return 0 on success
(except ​r_recvfrom() which should return the no. of bytes in the message) and -1 on
error.

● r_socket – This function opens an UDP socket with the ​socket call. It also
dynamically allocates space for all the tables, and initializes them. It sets the timer by
calling the ​setitimer() function. The parameters to these are the same as the
normal socket( ) call, except that it will take only SOCK_MRP as the socket type.
● r_bind​ – Binds the UDP socket with the specified address-port using the ​bind​ call.
● r_sendto – Adds a message id at the beginning of the message and puts it to the
send buffer if it is not full. If the send buffer is full, this function will (spin) block the
process until there is some space available to put this message.
● r_recvfrom – Looks up the ​received-message table to see if any message is
already received in the underlying UDP socket or not. If yes, it returns the first
message and deletes that message from the table. If not, it blocks the call. To block
the ​r_recvfrom call, you can use a sleep call to wait for some time and then see
again if a message is received. ​r_recvfrom​, similar to ​recvfrom,​ is a blocking call by
default and returns to the user only when a message is available.
● r_close – closes the socket; remove the timer and frees all memory associated with
the socket. If any data is there in the ​received-message table​, it is discarded.

Design the message formats and the u ​ nacknowledged-message table and the
received-message tables properly. Note that, sometimes sendto and recvfrom behave
differently during interrupt (i.e. signal). Consult the man pages and handle them
accordingly.

Testing your code:

1. Write a file ​rsocket.h​ that will contain


a. #includes for the sockets to work
b. #define for the timeout T (set to 2 seconds) and the drop probability ​p
(see description of ​dropMessage()​ function below)
c. prototypes of all the functions in the API listed above + a function
dropMessage() which is described below.
2. Write a file ​rsocket.c that contains all global variable declarations for the signal
handler and the data structures, and implementation of all the functions in the
API + ​dropMessage().​ This should NOT contain any main() function.
3. Create a static library called ​librsocket.a​ (look up the ​ar​ command in linux)
4. Write two test programs ​user1.c and ​user2.c.​ The program in ​user1.​ c will create
a MRP socket M1 and bind it to the port 50000+2*<your roll no> (for ex., if your
roll no. is 1001, the port no. is 52002). It then reads a long string from the
keyboard (25 < string size < 100), and sends each character of the string ​in a
separate message to user2.c.​ The messages are sent using M1 by making the
r​_​sendto calls. The program in ​user2.c will create a MRP socket M2 and bind it to
the port 50000+2*<your roll no> + 1 (for ex., if your roll no. is 1001, the port no. is
52002 + 1 = 52003). It then receives each message from ​user2.c using the
r_recvfrom call on M2 and immediately prints the character received on the
screen. Insert the ​dropMessage()​ function at the appropriate place (see below).
5. #include ​rsocket.h in user1.c and user2.c and compile them with ​librsocket.a (For
example, when we want to use functions in the math library like ​sqrt(),​ we include
math.h in our C file, and then link with the math library ​libm.a by using the flag
–lm during compilation)
6. Run ​user1 and ​user2 t​ o see if the strings are transmitted correctly. Also measure
the metric below by inserting appropriate code in your library.

dropMessage()​ function​:

As the actual number of drops in the lab environment will be near 0, you will need to
simulate an unreliable link. To do this, in the library, add a function called
dropMessage()​ with the following prototype in your library:

int dropMessage(float p)

where ​p is a probability between 0 and 1. This function first generates a random number
between 0 and 1. If the generated number is < ​p​, then the function returns 1, else it
returns 0. Now, in the signal handler code, after a message is received (by the
recv_from() call on the UDP socket), first make a call to ​dropMessage().​ If it returns 1, do
not take any action on the message (irrespective of whether it is data or ack) and just
return to wait to receive the next message. If it returns 0, continue with the normal
processing in the signal handler. Thus, if ​dropMessage() returns 1, the message
received is not processed and hence can be thought about as lost. ​Submit your code
with the ​dropMessage() calls in the signal handler, do NOT remove these calls
from your code before you submit.

The value of ​T should be 2 seconds (#define in ​rsocket.h​). The value of the parameter ​p
(the probability) should also be specified in the ​rsocket.h file with a #define. When you
test your program, vary ​p from 0.05 to 0.5 in steps of 0.05 (0.05, 0.1, 0.15, 0.2….,0.45,
0.5). For each ​p value, for the same string, count the average number of transmissions
made to send each character (​total number of transmissions that are made to send the
string /no. of characters in the string)​ . You can do this by adding a counter in the
appropriate part of the signal handler code. Report this in a table in the beginning of the
file ​documentation.txt​ (see below).

What to submit:
You should submit the following files in a single zip file:
1. rsocket.h
2. rsocket.c
3. Makefile (this should include all commands to create a library named ​librsocket.a
from your source files.)
4. A file called ​documentation.txt containing description of the different files. This file
should include:
a. The table described above for the no. of retransmissions
b. A list of all messages and their formats, with a brief description of the use
of each field
c. A list of all data structures used and a brief description of their fields

Do not include any other file in your submission, and upload the submission only
in zip format.

You might also like