Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

Your task is to implement the ReliableMessageSender and ReliableMessageReceiver classes in reliable_transport.py. ReliableMessageSender: This class reliably delivers a message to a receiver. You have to

Your task is to implement the ReliableMessageSender and ReliableMessageReceiver classes in reliable_transport.py.

ReliableMessageSender:

This class reliably delivers a message to a receiver. You have to implement the send_message and on_packet_received methods. You can use self.send(packet) to send a packet to the receiver. I am also attaching the reliable socket.py file below for help. You only need to edit the reliable transport.py. CODE BOTHTHESE METHODS IN PYTHON. IMPLEMENT THE SLIDING WINDOW MECHANISM. MAKE SURE YOU DIVIDE THE DATA INTO CHUNK SIZES BEFORE SENDING THE DATA AS PROVIDED IN THE INSTRUCTIONS.

Method descriptions:

__init__(self, ..., window_size: int) This is the constructor of the class where you can define any class attributes. window_size is the size of your message transport window (the number of in-flight packets during message transmission). Ignore other arguments; they are passed to the parent class. You should immediately return from this function and not block.

send_message(self, message: str) This method reliably sends the passed message to the receiver. This method does not need to spawn a new thread and return immediately; it can block indefinitely until the message is completely received by the receiver. You can send a packet to the receiver by calling self.send(...).

on_packet_received(self, packet: str) This method is invoked whenever a packet is received from the receiver. Ideally, only ACK packets should be received here. You would have to use a way to communicate these packets to the send_message method. One way is to use a queue: you can enqueue packets to it in this method, and dequeue them in send_message. You can also use the timeout argument of a queues dequeue method to implement timeouts in this assignment. You should immediately return from this method and not block.

ReliableMessageReceiver:

This class reliably receives a message from a sender. You have to implement the on_packet_received method. You can use self.send(packet) to send a packet back to the sender, and will have to call self.on_message_completed(message) when the complete message is received. Method descriptions:

__init__(self, ...) This is the constructor of the class where you can define any class attributes to maintain state. You should immediately return from this function and not block.

on_packet_received(self, packet: str) This method is invoked whenever a packet is received from the sender. You have to inspect the packet and determine what to do. You should immediately return from this method and not block. You can either ignore the packet, or send a corresponding ACK packet back to the sender by calling self.send(packet). If you determine that the sender has completely sent the message, call self.on_message_completed(message) with the completed message as its argument. Details on the role of a reliable receiver are provided in the next section.

Implementation Instructions

3.1 Packets

Youll need four types of packets in this assignment: "start", "end", "data", and "ack". All packets will also have a sequence number and a checksum. If the packet is a data packet, it will also contain the message content.

The packet format you have to follow is: "|||" An example start packet would be: "start|4212||1947410104"

For the message "request_users_list", with a chunk size of 10 bytes, the data packets would be: "data|4213|request_us|2826007388" "data|4214|ers_list|993936753"

You can use util.make_packet(...) to make a packet. It accepts packet_type, seq_num, and msg_content as arguments and returns a packet in the correct format. The returned packet also contains the checksum. For example, to make your start packet you can use the helper function as shown: start_packet = util.make_packet("start", start_seq_num) To validate the checksum, you can pass your packet to util.validate_checksum(...), which returns true/false accordingly. You can also use util.parse_packet(...) to parse a packet into its individual components (packet type, sequence number, data and checksum).

Implementing Reliable Transport

A message transmission will start from a start packet, and end with an end packet. You will be sending packets using a sliding window mechanism. For every received packet, the receiver will send a cumulative ACK with the sequence number it expects to receive next.

Senders logic: 1. Break down the message into util.CHUNK_SIZE sized chunks. 2. Choose a random sequence number to start the communication from. 3. Reliably send a start packet. (i.e. wait for its ACK and resend the packet if the ACK is not received within util.TIME_OUT seconds.) 4. Send out a window3 of data packets and wait for ACKs to slide the window appropriately. 5. How to slide the window? Suppose that the current window starts at sequence number j. If you receive an ACK of sequence number k, such that k > j, send the subsequent k j number of chunks. Note that the window now starts from sequence number j + (k j). However, please note that this is just one way to implement your sliding window. 6. If you receive no ACKs for util.TIME_OUT seconds, resend all the packets in the current window4 . 7. Once all the chunks have been reliably sent, reliably send an end packet.

UTIL.PY:

'''

This file contains basic utility functions that you can use.

'''

import binascii

MAX_NUM_CLIENTS = 10

TIME_OUT = 0.5 # 500ms

NUM_OF_RETRANSMISSIONS = 3

CHUNK_SIZE = 1400 # 1400 Bytes

def validate_checksum(message):

'''

Validates Checksum of a message and returns true/false

'''

try:

msg, checksum = message.rsplit('|', 1)

msg += '|'

return generate_checksum(msg.encode()) == checksum

except BaseException:

return False

def generate_checksum(message):

'''

Returns Checksum of the given message

'''

return str(binascii.crc32(message) & 0xffffffff)

def make_packet(pck_type="data", seqno=0, msg=""):

'''

This will add the packet header to your message.

The formats is `|||`

pck_type can be data, ack, end, start

seqno is a packet sequence number (integer)

msg is the actual message string

'''

body = "%s|%d|%s|" % (pck_type, seqno, msg)

checksum = generate_checksum(body.encode())

packet = "%s%s" % (body, checksum)

return packet

def parse_packet(packet):

'''

This function will parse the packet in the same way it was made in the above function.

'''

pieces = packet.split('|')

pck_type, seqno = pieces[0:2]

checksum = pieces[-1]

data = '|'.join(pieces[2:-1])

return pck_type, seqno, data, checksum

def make_message(msg_type, msg_format, message=None):

'''

This function can be used to format your message according

to any one of the formats described in the documentation.

msg_type defines type like join, disconnect etc.

msg_format is either 1,2,3 or 4

msg is remaining.

'''

if msg_format == 2:

return "%s" % (msg_type)

if msg_format in [1, 3, 4]:

return "%s %s" % (msg_type, message)

return ""

RELAIABLE TRANSPORT.PY:

from queue import Queue

from typing import Tuple

from socket import socket

Address = Tuple[str, int]

class MessageSender:

'''

DO NOT EDIT ANYTHING IN THIS CLASS

'''

def __init__(self, sock: socket, receiver_addr: Address, msg_id: int):

self.__sock: socket = sock

self.__receiver_addr = receiver_addr

self.__msg_id = msg_id

def send(self, packet: str):

self.__sock.sendto(

(f"s:{str(self.__msg_id)}:{packet}").encode("utf-8"),

self.__receiver_addr)

class ReliableMessageSender(MessageSender):

'''

This class reliably delivers a message to a receiver.

You have to implement the send_message and on_packet_received methods.

You can use self.send(packet) to send a packet to the receiver.

You can add as many helper functions as you want.

'''

def __init__(self, sock: socket, receiver_addr: Address, msg_id: int,

window_size: int):

MessageSender.__init__(self, sock, receiver_addr, msg_id)

'''

This is the constructor of the class where you can define any class attributes.

window_size is the size of your message transport window (the number of in-flight packets during message transmission).

Ignore other arguments; they are passed to the parent class.

You should immediately return from this function and not block.

'''

def on_packet_received(self, packet: str):

'''

TO BE IMPLEMENTED BY STUDENTS

This method is invoked whenever a packet is received from the receiver.

Ideally, only ACK packets should be received here.

You would have to use a way to communicate these packets to the send_message method.

One way is to use a queue: you can enqueue packets to it in this method, and dequeue them in send_message.

You can also use the timeout argument of a queues dequeue method to implement timeouts in this assignment.

You should immediately return from this method and not block.

'''

raise NotImplementedError

def send_message(self, message: str):

''''

TO BE IMPLEMENTED BY STUDENTS

This method reliably sends the passed message to the receiver.

This method does not need to spawn a new thread and return immediately; it can block indefinitely until the message is completely received by the receiver.

You can send a packet to the receiver by calling self.send(...).

Sender's logic:

1) Break down the message into util.CHUNK_SIZE sized chunks.

2) Choose a random sequence number to start the communication from.

3) Reliably send a start packet. (i.e. wait for its ACK and resend the packet if the ACK is not received within util.TIME_OUT seconds.)

4) Send out a window of data packets and wait for ACKs to slide the window appropriately.

5) How to slide the window? Suppose that the current window starts at sequence number j. If you receive an ACK of sequence number k, such that k > j, send the subsequent k j number of chunks. Note that the window now starts from sequence number j + (k j).

6) If you receive no ACKs for util.TIME_OUT seconds, resend all the packets in the current window.

7) Once all the chunks have been reliably sent, reliably send an end packet.

'''

raise NotImplementedError

class MessageReceiver:

'''

DO NOT EDIT ANYTHING IN THIS CLASS

'''

def __init__(self, sock: socket, sender_addr: Address, msg_id: int,

completed_message_q: Queue):

self.__sock: socket = sock

self.__sender_addr = sender_addr

self.__msg_id = msg_id

self.__completed_message_q = completed_message_q

def send(self, packet: str):

self.__sock.sendto(

(f"r:{str(self.__msg_id)}:{packet}").encode("utf-8"),

self.__sender_addr)

def on_message_completed(self, message: str):

self.__completed_message_q.put(message)

class ReliableMessageReceiver(MessageReceiver):

'''

This class reliably receives a message from a sender.

You have to implement the on_packet_received method.

You can use self.send(packet) to send a packet back to the sender, and will have to call self.on_message_completed(message) when the complete message is received.

You can add as many helper functions as you want.

'''

def __init__(self, sock: socket, sender_addr: Address, msg_id: int,

completed_message_q: Queue):

MessageReceiver.__init__(self, sock, sender_addr, msg_id,

completed_message_q)

'''

This is the constructor of the class where you can define any class attributes to maintain state.

You should immediately return from this function and not block.

'''

def on_packet_received(self, packet: str):

'''

TO BE IMPLEMENTED BY STUDENTS

This method is invoked whenever a packet is received from the sender.

You have to inspect the packet and determine what to do.

You should immediately return from this method and not block.

You can either ignore the packet, or send a corresponding ACK packet back to the sender by calling self.send(packet).

If you determine that the sender has completely sent the message, call self.on_message_completed(message) with the completed message as its argument.

Receivers logic:

1) When you receive a packet, validate its checksum and ignore it if it is corrupted.

2) Inspect the packet_type and sequence number.

3) If the packet type is "start", prepare to store incoming chunks of data in some data structure and send an ACK back to the sender with the received packets sequence number + 1.

4) If the packet type is "data", store it in an appropriate data type (if it is not a duplicate packet you already have stored), and send a corresponding cumulative ACK. (ACK with the sequence number for which all previous packets have been received).

5) If the packet type is "end", assemble all the stored chunks into a message, call self.on_message_received(message) with the completed message, and send an ACK with the received packets sequence number + 1.

'''

raise NotImplementedError

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

Professional Microsoft SQL Server 2014 Administration

Authors: Adam Jorgensen, Bradley Ball

1st Edition

111885926X, 9781118859261

More Books

Students also viewed these Databases questions

Question

Explain the reasons for the increase in inflation in countries?

Answered: 1 week ago