COMP347 (Revision 10) | TrustOpen University
Upon completion of this expanded tutorial, students will be able to:
Building on the foundational concepts of Tutorial 1, this tutorial provides a rigorous, mathematically‑informed exploration of how networked applications are structured and how their constituent processes communicate. We move beyond qualitative descriptions to formal models of scalability, fault tolerance, and performance.
We dissect the client‑server paradigm, exposing its fundamental bottlenecks and the techniques (load balancing, caching, replication) used to overcome them. We then turn to P2P systems, introducing the notion of overlay networks, distributed hash tables (DHTs), and the game‑theoretic incentives that encourage cooperation. The second half of the tutorial focuses on the mechanics of process communication: the socket API, addressing, and the crucial role of the operating system in demultiplexing. We also examine the trade‑offs in server concurrency models and the practical realities of NAT traversal.
Consider a server with upload capacity \( U_s \) (bits per second). It must serve a file of size \( F \) (bits) to \( N \) clients, each with download capacity \( d_i \). The minimum distribution time under the client‑server model is:
where \( d_{\min} = \min_i d_i \). The first term reflects the server bottleneck; the second reflects the slowest client. Importantly, \( T_{cs} \) does not depend on \( N \) except through the server’s ability to handle concurrent connections—but in practice, as \( N \) grows, the server must also manage increasing connection overhead (file descriptors, memory buffers), which can degrade \( U_s \) due to CPU and I/O contention.
Stateful vs stateless servers are a critical design choice. Stateless servers (e.g., REST APIs that store session data in client tokens or external caches) are easier to scale horizontally because any server can handle any request. Stateful servers require sticky sessions (session affinity), complicating load balancing and failover.
| Algorithm | Description | Pros / Cons |
|---|---|---|
| Round‑robin | Distributes requests sequentially across servers | Simple; ignores load differences |
| Least connections | Forwards request to the server with the fewest active connections | Better when requests have variable duration |
| Least response time | Uses real‑time response metrics to route | Requires monitoring; can react to performance degradation |
| IP hash | Hashes client IP to always route to the same server | Useful for session affinity |
P2P systems build a logical overlay network on top of the physical Internet. Nodes (peers) are connected by virtual links that may traverse multiple physical hops. Overlay topologies can be:
Assume the same file of size \( F \) is distributed to \( N \) peers. The server has upload \( U_s \); each peer \( i \) has upload \( u_i \) and download \( d_i \). The minimum distribution time is:
The third term is the key: as \( N \) grows, the total upload capacity \( U_s + \sum u_i \) typically grows linearly with \( N \) (if each peer has non‑zero upload), making \( T_{p2p} \) asymptotically constant or logarithmic, rather than linear. This is the fundamental self‑scaling property of P2P.
Churn refers to the continuous arrival and departure of peers. In a system with high churn, overlay maintenance becomes expensive. Strategies to mitigate churn include:
Free‑riding (consuming without contributing) is a classic public‑goods problem. BitTorrent’s tit‑for‑tat is a reciprocal strategy: peers unchoke those who have recently uploaded to them. It is shown to be a Nash equilibrium in certain game‑theoretic models. Other approaches include credit‑based systems (e.g., eMule’s queue‑based fairness) and reputation systems.
In hybrid systems, a subset of peers (superpeers) are more stable and have higher capacity. They act as local coordinators for groups of leaf peers. Superpeers maintain indexes and forward queries, while leaf peers only connect to a single superpeer. This reduces flooding overhead while preserving decentralisation. Examples: early Skype, Gnutella2.
While not strictly P2P, microservices represent a modern hybrid of client‑server and distributed autonomy. Services are small, independently deployable processes that communicate via lightweight protocols (HTTP/REST, gRPC). Service discovery (e.g., Consul, Eureka) and API gateways act as centralised coordination points, but the internal communication pattern is many‑to‑many, similar to P2P overlays.
The socket API provides a bidirectional communication endpoint. For TCP, the typical server sequence is:
socket() – creates a file descriptor for the socket.bind() – assigns a local IP and port (often omitted for clients).listen() – transitions the socket to a passive listening state; the kernel allocates a connection backlog queue.accept() – blocks until a connection arrives; returns a new connected socket for the client, while the listening socket remains open for further connections.read()/write() or recv()/send() – data transfer.close() – initiates TCP’s four‑way termination (FIN/ACK).
Each socket has a send buffer and a receive buffer in the kernel. send() copies data from user space to the kernel buffer; it may block if the buffer is full (TCP flow control). recv() copies data from the kernel buffer to user space.
Non‑blocking sockets return immediately with EAGAIN/EWOULDBLOCK if no data is ready. This enables event‑driven architectures (e.g., select(), poll(), epoll()) that handle thousands of connections with a single thread, avoiding the overhead of creating a thread per connection.
UDP sockets are datagram‑oriented. sendto() sends a datagram (message) with a destination address; recvfrom() receives a datagram and returns the source address. UDP is not connection‑oriented, so no listen()/accept() is needed. The kernel preserves message boundaries; a single recvfrom() will not return part of a datagram.
IPv4 uses 32‑bit addresses (~4.3 billion), but the Internet has outgrown this; hence NAT (Network Address Translation) is pervasive. IPv6 uses 128‑bit addresses, providing a virtually limitless namespace and restoring end‑to‑end connectivity.
The transport layer uses the destination port to demultiplex incoming segments. For TCP, the demultiplexing key is the 4‑tuple (src_ip, src_port, dst_ip, dst_port), allowing multiple simultaneous connections to the same server port. For UDP, it is only (dst_ip, dst_port) because UDP is connectionless; however, many implementations also consider the source address to improve security.
NAT devices map internal private IPs to a public IP and modify port numbers. This breaks incoming connections. Solutions include:
| Metric | TCP | UDP |
|---|---|---|
| Header overhead | 20–60 bytes | 8 bytes |
| Connection setup RTT | 1 RTT (3‑way handshake) + TLS (if used) | 0 RTT |
| Per‑packet processing | Higher (checksum, sequence, ACK, timer) | Very low |
| Flow / congestion control | Yes | None (application must implement) |
Process one request at a time; simple but cannot handle concurrent clients. Only suitable for trivial applications or low‑load scenarios.
For each accepted connection, spawn a new thread or process. This is easy to program but does not scale well (memory, context‑switch overhead). Used with thread pools to amortise overhead.
Use a single thread with an event loop (e.g., epoll, kqueue, IOCP). Non‑blocking I/O allows one thread to handle thousands of concurrent connections. This is the basis of high‑performance servers like Nginx, Node.js, and Redis.
The Reactor pattern demultiplexes events and dispatches them to handlers. The Proactor pattern (used in Windows IOCP) uses asynchronous I/O where the OS notifies completion.
The application issues I/O operations and continues; the kernel signals completion via callback or event. This can yield even higher throughput but is more complex to program and debug.
Q1: In the client‑server distribution time model, what does \( U_s \) represent?
The upload capacity of the server (bits per second).
Q2: Why can P2P distribution time be asymptotically constant while client‑server grows linearly with N?
In P2P, total upload capacity grows with N, so the term \( N \cdot F / (U_s + \sum u_i) \) does not grow with N.
Q3: What is the difference between a listening socket and a connected socket in TCP?
A listening socket accepts incoming connections; a connected socket represents an established connection with a specific client.
Q4: What is the demultiplexing key for UDP?
Primarily the destination port (and often destination IP; some implementations also use source address).
Q5: Name two NAT traversal techniques and briefly describe each.
STUN (client discovers public IP/port via an external server) and TURN (traffic is relayed through a server when direct connection fails).
Q6: Why is TCP not ideal for real‑time interactive voice applications?
TCP’s congestion control and retransmissions can introduce variable delay (jitter) and head‑of‑line blocking; voice applications tolerate loss better than delay.
Q7: What is the role of the accept() system call in a TCP server?
It extracts the first connection from the listening socket’s backlog queue and creates a new connected socket for that client.
Q8: In the event‑driven (Reactor) model, what primitive is used to efficiently wait for I/O events on many sockets?
epoll (Linux), kqueue (BSD), or IOCP (Windows).
Q9: What is free‑riding in P2P systems and how does tit‑for‑tat mitigate it?
Free‑riding is when peers consume resources without contributing. Tit‑for‑tat encourages contribution by prioritising upload to peers who have recently uploaded to the client.
Q10: What is the purpose of the SO_REUSEADDR socket option?
It allows a socket to bind to a port that is in the TIME_WAIT state, enabling rapid server restarts without waiting for the timeout to expire.
Q11: What is the difference between horizontal and vertical scaling?
Vertical scaling adds more power (CPU/RAM) to a single machine; horizontal scaling adds more machines and distributes the load.
Q12: Why are DHTs (e.g., Kademlia) used in structured P2P networks?
They provide efficient, deterministic lookup of keys (e.g., file hashes) in \( O(\log N) \) hops without a central index.
Exercise 1 – Distribution time calculation
Given: \( F = 100 \) MB, \( U_s = 10 \) Mbps, \( N = 1000 \) peers, each with \( u_i = 1 \) Mbps, \( d_i = 10 \) Mbps. Calculate \( T_{cs} \) and \( T_{p2p} \).
\( F = 100 \times 8 = 800 \) Mb.
\( T_{cs} = \max(NF/U_s, F/d_{\min}) = \max(1000*800/10, 800/10) = \max(80000, 80) = 80,000 \) s ≈ 22.2 hours.
\( T_{p2p} = \max(F/U_s, F/d_{\min}, NF/(U_s + \sum u_i)) = \max(80, 80, 800000/(10 + 1000)) = \max(80, 80, 792.08) = 792 \) s ≈ 13.2 minutes.
Conclusion: P2P is dramatically faster (22 hours vs 13 minutes).
Exercise 2 – Socket calls for UDP echo
Write the sequence of system calls for a UDP echo server and client.
Server: socket() → bind() → loop { recvfrom() → sendto() }.
Client: socket() → sendto() → recvfrom() → close().
Exercise 3 – NAT traversal scenario
Two peers behind symmetric NATs. Which technique is required? Explain.
Symmetric NATs assign a different public port for each destination. STUN usually fails because the port mapping changes per destination. TURN (relay) is required, as it does not depend on the peer’s NAT behaviour.
Exercise 4 – Concurrency model selection
A server handles long‑lived WebSocket connections. Should it use thread‑per‑connection or event‑driven? Justify.
Event‑driven (e.g., epoll) is preferable because WebSocket connections are long‑lived and often idle; threading would waste memory and cause context‑switch overhead. Event loops handle thousands of idle connections efficiently.
Exercise 5 – Hybrid architecture design
Sketch a P2P file‑sharing system that uses a DHT for peer discovery but also has a fallback tracker. Discuss failure scenarios.
The system uses Kademlia DHT as primary. If DHT lookup fails (due to high churn), the client contacts a well‑known fallback tracker (centralised). The tracker can also help bootstrap the DHT. Failure scenarios: if the DHT is partitioned, the tracker provides a consistent view; if the tracker is down, the DHT still works.
Exercise 6 – Analyse the effect of TTL in flooding‑based P2P
Explain how the TTL (Time‑To‑Live) in a query message affects scalability and success rate.
A higher TTL increases the reach (more peers) but also increases the number of messages exponentially (flooding), causing network congestion. A lower TTL reduces overhead but may miss rare resources. Adaptive TTL or expanding‑ring search (start with low TTL, increase on failure) is a common optimisation.
Homework 1 – Churn Modelling
Simulate (conceptually) a P2P network with peer arrival rate \( \lambda \) and departure rate \( \mu \). Derive the expected lifetime of a peer and the probability that a file remains available if replicated at \( k \) peers.
Use M/M/∞ queue analogy; expected lifetime = \( 1/\mu \); availability probability = \( 1 - (1 - e^{-\mu t})^k \) for a given time \( t \).
Homework 2 – Socket Option Investigation
Research and explain the purpose of TCP_NODELAY and SO_LINGER. In which situations would you enable them?
TCP_NODELAY disables Nagle’s algorithm (reduces latency for small packets, useful for gaming). SO_LINGER controls behaviour when closing a socket with unsent data.
Homework 3 – Load Balancing Algorithms
Compare the effectiveness of round‑robin vs least‑connections for a web server farm where request sizes vary widely (some are static images, others are heavy database queries).
Least‑connections is better when request processing times differ; round‑robin may overload servers with long‑running requests.
Homework 4 – P2P Incentive Design
Propose a reputation‑based incentive system for a P2P storage network. Define reputation scores, update rules, and how they affect storage allocation.
Reputation could increase when peers reliably serve data, and decrease when they go offline or refuse uploads. Storage could be allocated proportionally to reputation.
Homework 5 – Real‑World Architecture Analysis
Choose a modern application (e.g., WhatsApp, Zoom, Spotify) and identify its architecture (client‑server, P2P, hybrid, or microservices). Explain how the architecture supports its scale and real‑time requirements.
For Zoom, it uses a mix: signaling is client‑server, media is often P2P (or relayed via TURN when needed). For Spotify, streaming is client‑server with CDN, but the discovery and social features are microservices.
This expanded tutorial has provided a rigorous, multi‑faceted view of application architectures and process communication. The key takeaways are:
These principles will be applied in the following tutorials to specific protocols: HTTP, SMTP, DNS, and BitTorrent, where we will see how architectural choices manifest in real‑world protocol designs.