Python Socket Programming and CRC Implementation Techniques

Python Networking and Data Integrity Techniques

This document provides practical Python implementations for fundamental networking tasks using the socket module, alongside a crucial data integrity mechanism: Cyclic Redundancy Check (CRC).

1. Building a Basic HTTP Client

Downloading a Webpage via TCP Socket

This function demonstrates how to manually establish a TCP connection to a host on port 80 and send a raw HTTP GET request to retrieve content.

import socket

def download_webpage(host, path="/"):
    # Create a TCP socket (IPv4, Stream)
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Connect to the host (HTTP port 80)
    client_socket.connect((host, 80))
    
    # Send an HTTP GET request
    request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
    client_socket.send(request.encode())
    
    # Receive the response from the server
    response = b""
    while True:
        part = client_socket.recv(4096)
        if not part:
            break
        response += part
        
    # Close the socket
    client_socket.close()
    
    # Decode and return the response (ignoring potential errors)
    return response.decode(errors="ignore")

# Example usage
host = "google.com"
page = download_webpage(host)
print(page)

2. Implementing TCP Client-Server Communication

The following examples illustrate the fundamental setup for a local TCP server listening on port 8080 and a corresponding client that connects and exchanges messages.

TCP Server Setup

The server binds to an address and port, listens for connections, accepts them, receives data, sends a response, and closes the client connection.

import socket

def start_server():
    # Create a socket object
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Bind the socket to an address and port
    server_socket.bind(('localhost', 8080))
    
    # Listen for incoming connections (max 5 connections queued)
    server_socket.listen(5)
    print("Server is listening on port 8080...")
    
    while True:
        # Accept a new connection
        client_socket, client_address = server_socket.accept()
        print(f"Connection from {client_address} has been established.")
        
        # Receive data from the client
        data = client_socket.recv(1024).decode('utf-8')
        print(f"Received from client: {data}")
        
        # Send a response back to the client
        response = f"Server received: {data}"
        client_socket.send(response.encode('utf-8'))
        
        # Close the client socket
        client_socket.close()

# start_server()

TCP Client Implementation

The client connects to the specified server address, sends a message, waits for a response, and then closes the connection.

import socket

def start_client():
    # Create a socket object
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Connect to the server
    client_socket.connect(('localhost', 8080))
    
    # Send a message to the server
    message = "Hello, Server!"
    client_socket.send(message.encode('utf-8'))
    
    # Receive a response from the server
    response = client_socket.recv(1024).decode('utf-8')
    print(f"Server response: {response}")
    
    # Close the socket
    client_socket.close()

# start_client()

3. Cyclic Redundancy Check (CRC) Implementation

CRC is an error-detecting code used to verify data integrity during transmission. This implementation uses Modulo-2 division on binary strings.

Core CRC Functions for Data Integrity

The implementation relies on three core functions: xor for bitwise comparison, mod2div for the division process, and encode_data/decode_data for handling the data frame.

def xor(a, b):
    """Performs XOR operation on two binary strings of equal length."""
    result = []
    for i in range(len(a)):
        result.append('0' if a[i] == b[i] else '1')
    return "".join(result)

def mod2div(dividend, divisor):
    """Performs Modulo-2 division."""
    N = len(divisor)
    tmp = dividend[:N]
    i = N
    
    while i < len(dividend):
        if tmp[0] == '1':
            # XOR with divisor
            tmp = xor(divisor, tmp) + dividend[i]
        else:
            # XOR with zero string
            tmp = xor('0' * N, tmp) + dividend[i]
        
        # Remove the leading bit
        tmp = tmp[1:]
        i += 1
        
    # Final XOR operation to get the remainder
    if tmp[0] == '1':
        tmp = xor(divisor, tmp)
    else:
        tmp = xor('0' * N, tmp)
        
    # Return the remainder (excluding the leading bit)
    return tmp[1:]

def encode_data(data, key):
    """Encodes data using CRC by appending the remainder."""
    N = len(key)
    # Append N-1 zeros to the data
    appended_data = data + '0' * (N - 1)
    
    # Calculate remainder
    remainder = mod2div(appended_data, key)
    
    # The codeword is the original data plus the remainder
    codeword = data + remainder
    return codeword

def decode_data(data, key):
    """Decodes data and returns the remainder for error checking."""
    remainder = mod2div(data, key)
    return remainder

Example Usage and Error Detection

This example demonstrates encoding a binary string and then checking the resulting codeword for errors upon reception.

if __name__ == "__main__":
    # Example data to be transmitted
    data = "1011001"  # Binary data
    key = "1101"      # Divisor (CRC key)
    
    print("Original Data:", data)
    
    # Sender side: encoding
    codeword = encode_data(data, key)
    print("Encoded Data (Codeword):", codeword)
    
    # Receiver side: decoding
    received_data = codeword 
    # In a real scenario, 'received_data' might be corrupted
    
    remainder = decode_data(received_data, key)
    
    # Check if there is an error (remainder should be all zeros if no error)
    print(f"Calculated Remainder: {remainder}")
    
    if "1" in remainder:
        print("Error detected in received data.")
    else:
        print("No error detected in received data.")