Code for my computer networks (Rechnernetze) lecture at University of Klagenfurt (https://itec.aau.at/)
The examples are plain Python scripts, grouped by topic. Each folder is
self-contained and has its own README.
make venv # create .venv and install dependencies
source .venv/bin/activate
Or without make:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
python 01_thread_creation/thread_subclass.py # run a single example
make format # auto-format and fix imports (ruff)
make lint # style and error checks (ruff)
make clean # remove caches and build artifacts
Chapters 1-7 use only the standard library (threading, socket, abc,
random, time) and run on a bare Python 3.9+ without the virtual
environment. Chapter 8 is the exception: QUIC is not in the standard library,
so it needs aioquic from requirements.txt.
The socket examples come in pairs: start the server in one terminal and run
the client in another.
| File |
Description |
thread_subclass.py |
Thread as a subclass of threading.Thread, overriding run() |
thread_target.py |
Work object handed to a thread via target= |
fork_join.py |
Call vs. fork vs. join, made visible |
| File |
Description |
bounded_buffer_mon.py |
Ring buffer as a monitor: threading.Condition, wait / notify_all |
semaphore.py |
Dijkstra's counting semaphore (P/V) built from a monitor |
bounded_buffer_sem.py |
Same buffer with the empty, full and mutex semaphores |
buf_user.py |
Producer and consumer threads plus the driver |
| File |
Description |
forks_mon.py |
Fork management as a monitor — deadlock-free |
forks_sem.py |
Semaphore variants with and without the room semaphore |
fork_user.py |
Philosopher threads and the driver; deadlock mode deadlocks on purpose |
| File |
Description |
rw.py |
Access control with writer preference |
rw_user.py |
Reader and writer threads plus the driver |
| File |
Description |
tcp_server.py |
Welcoming socket, one connection socket per client |
tcp_client.py |
Connects, sends a line, prints the reply |
| File |
Description |
udp_server.py |
One socket serving every client |
udp_client.py |
Sends a datagram, prints the reply, times out if none comes |
| File |
Description |
udp_flood_client.py |
Sends 1024-byte packets as fast as it can, numbered |
udp_flood_server.py |
Counts arrivals, detects gaps, reports how many were lost |
| File |
Description |
quic_common.py |
ALPN name, port, throwaway self-signed certificate |
quic_server.py |
Listens on UDP 4433, one handler per stream |
quic_client.py |
Opens a stream, sends a line, prints the reply |
| File |
Description |
rdt_common.py |
Packet format and a deliberately lossy channel |
rdt_receiver.py |
Two receivers: cumulative, and one that buffers early arrivals |
rdt_sender.py |
Three senders: stop-and-wait, go-back-N, selective repeat |
rdt_compare.py |
Runs all three over the same channel and compares them |
rdt_dilemma.py |
Why a selective-repeat window may be at most half the sequence space |
CPython serialises the execution of Python bytecode with a global interpreter
lock, so threads give concurrency — quasi-parallel execution on a shared
processor — rather than CPU parallelism. Critical sections, monitors and
semaphores behave exactly as described, and threads do overlap during I/O and
sleep(). For truly parallel CPU-bound work use multiprocessing; since 3.13
CPython also offers an experimental free-threaded build without the GIL.