Python – Stop & Wait Implementation using CRC
Last Updated :
21 Dec, 2022
Stop and wait protocol is an error control protocol, in this protocol the sender sends data packets one at a time and waits for positive acknowledgment from the receiver’s side, if acknowledgment is received then the sender sends the next data packet else it’ll resend the previous packet until a positive acknowledgment is not received.
Note: To get more info on what is stop and wait protocol, refer Stop and Wait ARQ article.
CRC aka Cyclic redundancy check is an error detection mechanism, its procedure is as follows.
Sender side
- Choose a generator polynomial mutually agreed upon by the sender and receiver, let k be the number of bits in the key obtained from this polynomial.
- Append (k – 1) 0’s to the right of the actual binary data.
- Divide the data obtained in step 2 by the key, and store the remainder.
- Append the remainder to the actual binary data, and send the data packet.
Receiver side
- Divide the received data bits by the key.
- If the remainder is non-zero, then the data is corrupted.
In this article, we will implement stop and wait algorithm such that the sender computes error detecting code using CRC and sends the data along with error detecting code to the receiver using socket. The receiver, after ensuring an error-free frame, saves the data.
The procedure will be as follows:
At sender
- Read input from a file, n characters at a time.
- Convert it into binary
- Generate its CRC.
- Send data + CRC bits to the receiver, but before sending, randomly introduce error
At receiver:
- Receive data packet.
- Determine if it is error-free. If yes extract data bits, convert them into character form, and save them into the output file. Send Ack as OK. If not, send NAK.
At sender
- If OK is received, proceed with the next n characters. Otherwise, if NAK is received, send the data + CRC bits again to the receiver after randomly introducing the error.
Final outcome: Input and output files should match. A log file should show how many frames were in error and how many retries were done for each frame.
Error Generation
Error is introduced as follows :
- Generate a random number, say r1. Perform r1 % 2. If you get a 0 do not introduce error and send original bits. If you get a 1, introduce error.
- To decide which bit will be in error, generate another random number, say r2. Perform r2 %(size of received frame). Assume you get a value i as the outcome. Flip the i-th bit. Now send it to the receiver.
Save this file as error_gen.py.
Python3
import random
class err_gen() :
def induce_err( self , in_str) :
chk = ( int )(random.random() * 1000 ) % 2
if not chk :
return in_str
idx = ( int )(random.random() * 1000 ) % len (in_str)
f_bit = '*'
if in_str[idx] = = '0' :
f_bit = '1'
else :
f_bit = '0'
out_str = in_str[ : idx] + f_bit + in_str[idx + 1 : ]
return out_str
if __name__ = = "__main__" :
data = "1001010"
print ( "Initial : " , data)
print ( "Final : " , err_gen().induce_err(data))
|
Output:
Initial : 1001010
Final : 0001010
Modulo two Division
Modulo two-division is required to calculate the CRC. Explaining the working of this code is beyond the scope of this article, the curious ones can refer Modulo two division.
Save this file as calc.py.
Python3
def xor(a, b):
result = []
for i in range ( 1 , len (b)):
if a[i] = = b[i]:
result.append( '0' )
else :
result.append( '1' )
return ''.join(result)
def mod2div(dividend, divisor):
pick = len (divisor)
tmp = dividend[ 0 : pick]
while pick < len (dividend):
if tmp[ 0 ] = = '1' :
tmp = xor(divisor, tmp) + dividend[pick]
else :
tmp = xor( '0' * pick, tmp) + dividend[pick]
pick + = 1
if tmp[ 0 ] = = '1' :
tmp = xor(divisor, tmp)
else :
tmp = xor( '0' * pick, tmp)
return tmp
if __name__ = = "__main__" :
dividend = "10010101"
divisor = "011010"
print (dividend + " % " + divisor + " = " + mod2div(dividend, divisor))
|
Output:
10010101 % 011010 = 01001
Configuration Data
These are some constants that are shared by the receiver and the sender save this file as configuration.py.
Python3
FRAME_SIZE = 10
BUFFER_SIZE = 1024 * 10
END_OF_FILE = "##**##**##**##**##"
CRC_GENERATOR = "10110100110101110011010101110100000101"
REJECT = "NAK"
ACCEPT = "OK"
|
Client
The client class will have five methods.
- Constructor: To connect to the server using a socket at the given IP address and port number.
- asciiToBin: To convert ASCII string to binary string.
- appendZero: To append (k – 1) 0’s to the end of binary data.
- encode: To generate and append CRC at the end of actual data bits.
- sendfile :
- This method reads n characters from the input file at a time &
- Creates the data packet to be sent by calling the encode method.
- Calls induce_error method to randomly introduce an error in the data packet.
- Sends the data packet and waits for the acknowledgment.
- If the acknowledgment received is positive, then move on to the next n bits,
- else resend the current data packet.
- When the file is completely read, then send a flag to tell the receiver to stop waiting for the next frame.
- Terminate the session.
Save this file as CLIENT.py.
Python3
from calc import mod2div
from error_gen import err_gen
from configuration import *
import socket
class Client:
def __init__( self , ipadd, portn):
self .socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self .socket_.connect((ipadd, portn))
def asciiToBin( self , data):
return bin ( int .from_bytes(data.encode(), 'big' ))
def appendZero( self , message):
message = (message.ljust( len (CRC_GENERATOR) - 1 + len (message), '0' ))
return message
def encode( self , data):
message = self .asciiToBin(data)
dividend = self .appendZero(message)
crc = mod2div(dividend, CRC_GENERATOR)
curr_frame = (message + crc)
return curr_frame
def send_file( self , filename = 'file.txt' ):
f = open (filename)
data = f.read(FRAME_SIZE)
while len (data) > 0 :
curr_frame = self .encode(data)
curr_frame = err_gen().induce_err(curr_frame)
self .socket_.send(curr_frame.encode())
if self .socket_.recv(BUFFER_SIZE).decode() = = 'OK' :
data = f.read(FRAME_SIZE)
self .socket_.send(END_OF_FILE.encode())
self .socket_.close()
f.close()
print ( "File sent" )
newclient = Client(ipadd = "127.0.0.1" , portn = 3241 )
newclient.send_file(filename = "file.txt" )
|
Server
The server class will have six methods.
- Constructor: To listen for client request at the given IP address and port number.
- iszero : To determine if a string represents 0.
- isCurrupted : To determine if the received data is corrupted by performing modulo 2 division by the CRC_GENERATOR.
- decode : To extract the data bits from the received data packet and convert them to their ASCII values.
- log : To log the entry of each frame in the logfile.
- receive_file :
- This method receives the data packet from the sender &
- Checks its validity by calling the isCurrupted function.
- If the data packet is valid, then it decodes it and copies it in a file at server’s end and sends a positive acknowledgement to the sender and logs the entry of the data packet in the logfile.
- else, it sends a negative acknowledgement to the sender.
- If the received data packet is the end of file, then it terminates all the connections and returns.
Save this file as SERVER.py
Python3
import socket
from calc import mod2div
from configuration import *
class Server:
def __init__( self , ipaddr, portn):
self .socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self .socket_.bind((ipaddr, portn))
self .socket_.listen( 5 )
def iszero( self , data):
for x in data:
if x ! = '0' :
return False
return True
def isCurrupted( self , message):
return not self .iszero(mod2div(message, CRC_GENERATOR))
def decode( self , message):
message = message[: 1 - len (CRC_GENERATOR)]
n = int (message, 2 )
return n.to_bytes((n.bit_length() + 7 ) / / 8 , 'big' ).decode()
def log( self , loghandle, itr, received_frame, retrie_count):
loghandle.write( "Frame Number : " + str (itr) + "\n" )
loghandle.write( "Frame Content : \"" +
self .decode(received_frame) + "\"\n" )
loghandle.write( "Retries : " + str (retrie_count) + "\n\n" )
def receive_file( self , filepath, logpath):
received_socket, addr = self .socket_.accept()
f = open (filepath, 'w' )
l = open (logpath, 'w' )
itr = 1
retrie_count = 0
while 1 :
received_frame = received_socket.recv(BUFFER_SIZE).decode()
if received_frame = = END_OF_FILE:
f.close()
l.close()
self .socket_.close()
print ( "File Received" )
return
if self .isCurrupted(received_frame):
retrie_count + = 1
received_socket.send(REJECT.encode())
else :
f.write( self .decode(received_frame))
self .log(l, itr, received_frame, retrie_count)
retrie_count = 0
received_socket.send(ACCEPT.encode())
newServer = Server(ipaddr = "127.0.0.1" , portn = 3241 )
newServer.receive_file(filepath = "received_data.txt" , logpath = "logfile.txt" )
|
To see the execution of the code, keep all the files in a single directory, and execute the server file before executing the client’s file. Also, make sure that you have the input text file and its correct path.
Execution:
Program execution process
Share your thoughts in the comments
Please Login to comment...