Systems Programming & Concurrency

Exploration of low-level OS concepts including socket lifecycle management, process forking, and multi-threaded UI applications.

Python (Sockets) Multithreading Unix Process Forking Networking Protocols

Project Overview

This collection of scripts demonstrates fundamental systems programming concepts. By interacting directly with OS-level APIs, I built robust networking tools and concurrent applications that manage shared resources and process states.

The project covers the implementing of a raw TCP server, managing the "Zombie Process" lifecycle through forking, and building a thread-safe real-time plotting application where the UI loop is decoupled from Input I/O.

Key Concepts Implemented

TCP Socket Lifecycle

Handled low-level socket operations (`bind`, `listen`, `accept`, `shutdown`) to create a robust Time Server that adheres to TCP protocols.

Process Forking

Demonstrated Unix process management by using `os.fork()` to spawn child processes, executing system commands (`ls -l`) concurrently with the parent.

Thread-Safe UI

Implemented a multi-threaded architecture where a daemon thread handles blocking user input while the main thread renders real-time animations, preventing UI freezes.

Source Code

1. TCP Time Server (`p7_hw6.py`)

A server that binds to a port and serves the current time to connected clients.

#!/usr/bin/env python3
#I am using code from server.py
import socket
import time

def bind_port(prt):
   """Create socket and bind to port prt.
   """
   host = ''  
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   s.bind((host, prt))
   s.listen(1)
   return(s)
    
if __name__ == '__main__':
   port = 55555
   print('Time Server starting on port %d...' % port)
   print('Press Ctrl+C to stop the server.')
   thesocket = bind_port(port)
   while True:
      connection, peer = thesocket.accept()
      current_time = time.ctime()
      message = '\n\nCurrent time: %s\n\n' % current_time
      outdata = message.encode('utf-8')
      print('Sending time to %s...' % repr(peer), end='')
      connection.sendall(outdata) 
      print('Done.')
      connection.shutdown(socket.SHUT_RDWR)
      connection.close()

2. Process Forking (`p4_b_hw8.py`)

Spawns a child process every 10 iterations to execute a system command.

#!/usr/bin/env python3
import os
import time

i = 1
while True:
    print(i)    
    if i % 10 == 0:
        print("About to fork...")
        retval = os.fork()        
        if retval == 0:  
            print("Child process: About to execute ls -l")
            os.execv('/bin/ls', ['ls', '-l'])
    time.sleep(0.5)
    i += 1

3. Threaded Real-Time Plotter (`p5_hw8.py`)

Updates a live graph based on user input without blocking the animation loop.

#!/usr/bin/env python3
# using logic from thread_example

import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
import threading
import sys

current_val = 0.0

def get_input():
    """Continuously prompts user for input to update global variable."""
    global current_val
    print("Plot running. Enter numbers to update. The range of y-values visible in this graph is from -10 to 10 so keep that in mind.")
    while True:
        try:
            current_val = float(input("New value: "))
        except ValueError:
            print("Input must be a number.", file=sys.stderr)

class Scope():
    def __init__(self, ax, maxt=10, dt=0.02):
        self.ax = ax
        self.dt = dt
        self.maxt = maxt
        self.tdata = np.array([])
        self.ydata = np.array([])
        self.t0 = time.perf_counter()
        self.line = Line2D(self.tdata, self.ydata)
        self.ax.add_line(self.line)
        self.ax.set_ylim(-10, 10) 
        self.ax.set_xlim(0, self.maxt)

    def update(self, data):
        t, y = data
        self.tdata = np.append(self.tdata, t)
        self.ydata = np.append(self.ydata, y)
        self.ydata = self.ydata[self.tdata > (t - self.maxt)]
        self.tdata = self.tdata[self.tdata > (t - self.maxt)]
        self.line.set_data(self.tdata, self.ydata)
        if len(self.tdata) > 0:
            self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt) 
        self.ax.figure.canvas.draw()
        return self.line,

    def emitter(self):
        while True:
            t = time.perf_counter() - self.t0
            yield t, current_val

if __name__ == '__main__':
    input_thr = threading.Thread(target=get_input)
    input_thr.daemon = True 
    input_thr.start()
    dt = 0.01
    fig, ax = plt.subplots()
    scope = Scope(ax, maxt=10, dt=dt)
    ani = animation.FuncAnimation(fig, scope.update, scope.emitter, interval=dt*1000., blit=True)
    plt.show()