├── bin ├── client └── server ├── client.go ├── README.md └── server.go /bin/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritiksahni/cwnd-demo/HEAD/bin/client -------------------------------------------------------------------------------- /bin/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritiksahni/cwnd-demo/HEAD/bin/server -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | func logWithTimestamp(message string) { 11 | fmt.Printf("%s - %s\n", time.Now().Format("2006-01-02 15:04:05"), message) 12 | } 13 | 14 | func main() { 15 | conn, err := net.Dial("tcp", "localhost:8080") 16 | if err != nil { 17 | logWithTimestamp(fmt.Sprintf("Error connecting to server: %v", err)) 18 | return 19 | } 20 | defer conn.Close() 21 | 22 | logWithTimestamp("Connected to server. Waiting for data...") 23 | 24 | reader := bufio.NewReader(conn) 25 | 26 | for { 27 | message, err := reader.ReadString('\n') 28 | if err != nil { 29 | logWithTimestamp("Connection closed by server.") 30 | return 31 | } 32 | 33 | logWithTimestamp(fmt.Sprintf("Received: %s", message)) 34 | 35 | _, err = fmt.Fprintf(conn, "ACK\n") 36 | if err != nil { 37 | logWithTimestamp(fmt.Sprintf("Error sending ACK: %v", err)) 38 | return 39 | } 40 | 41 | logWithTimestamp("Sent: ACK") 42 | 43 | time.Sleep(500 * time.Millisecond) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TCP Slow Start Simulation in Go 2 | 3 | This repository contains a simple simulation of TCP's **slow-start** congestion control mechanism, implemented in Go. It demonstrates how the congestion window (cwnd) grows exponentially during the slow-start phase and how the server reacts to packet loss by reducing the window size. 4 | 5 | This is inspired by [https://hpbn.co/building-blocks-of-tcp/#slow-start](https://hpbn.co/building-blocks-of-tcp/#slow-start) 6 | 7 | It's a brilliant piece on the building blocks of TCP and how "slow start" works. 8 | 9 | ## Overview 10 | 11 | ### Key Concepts: 12 | - **Slow Start**: TCP starts with a small congestion window (cwnd) and exponentially increases it after every successful acknowledgment (ACK), until a packet loss occurs. 13 | - **Congestion Window (cwnd)**: Controls the number of packets the server can send without receiving an ACK. Starts small and grows during the slow-start phase. 14 | - **Packet Loss**: Simulated with a probability threshold, causing the congestion window to reduce by half. 15 | 16 | The server sends data packets based on the current congestion window size and waits for ACKs from the client. On receiving ACKs, the congestion window increases. If a packet loss is detected, the window size is reduced. 17 | 18 | ### Features: 19 | - Logs with timestamps for better visualization of the slow-start process and packet loss handling. 20 | - Simulated round-trip times (RTT) and network delays. 21 | - A simple random loss simulator to emulate packet drops in a network. 22 | 23 | ## Usage 24 | 25 | ### 1. Run the Server 26 | Start the server, which listens for incoming client connections and begins sending packets while applying TCP slow-start behavior. 27 | 28 | ```bash 29 | go run server.go 30 | ``` 31 | 32 | ### 2. Run the Client 33 | The client connects to the server and receives data while sending ACKs back for each batch of packets received. 34 | 35 | ```bash 36 | go run client.go 37 | ``` 38 | 39 | ### 3. Output 40 | Both server and client log events such as packet transmissions, ACKs, congestion window updates, and packet loss. Logs include timestamps to show the progression of each round-trip time (RTT) cycle. 41 | 42 | ## Example Logs 43 | 44 | **Server:** 45 | ``` 46 | 2024-10-19 12:30:01 - Server is listening on localhost:8080 47 | 2024-10-19 12:30:05 - Current congestion window (cwnd): 1 48 | 2024-10-19 12:30:06 - Sent: Packet 1 49 | 2024-10-19 12:30:06 - Received ACK from client. 50 | 2024-10-19 12:30:06 - ACK successful, increasing cwnd. New cwnd = 2 51 | ... 52 | ``` 53 | 54 | **Client:** 55 | ``` 56 | 2024-10-19 12:30:05 - Connected to server. Waiting for data... 57 | 2024-10-19 12:30:05 - Received: Packet 1 58 | 2024-10-19 12:30:05 - Sent: ACK 59 | ... 60 | ``` 61 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | const ( 10 | initialCwnd = 1 // Initial congestion window size (packets) 11 | maxCwnd = 10 // Maximum congestion window size 12 | ) 13 | 14 | func logWithTimestamp(message string) { 15 | fmt.Printf("%s - %s\n", time.Now().Format("2006-01-02 15:04:05"), message) 16 | } 17 | 18 | func handleConnection(conn net.Conn) { 19 | defer conn.Close() 20 | 21 | cwnd := initialCwnd 22 | lossThreshold := 0.2 // Simulate 20% chance of packet loss 23 | packetCount := 0 24 | 25 | logWithTimestamp("New client connected, starting data transmission.") 26 | 27 | for { 28 | logWithTimestamp(fmt.Sprintf("Current congestion window (cwnd): %d", cwnd)) 29 | for i := 0; i < cwnd; i++ { 30 | packetCount++ 31 | // Write data (packet) to the client 32 | _, err := fmt.Fprintf(conn, "Packet %d\n", packetCount) 33 | if err != nil { 34 | logWithTimestamp("Connection closed by client.") 35 | return 36 | } 37 | logWithTimestamp(fmt.Sprintf("Sent: Packet %d", packetCount)) 38 | } 39 | 40 | time.Sleep(1 * time.Second) 41 | 42 | buf := make([]byte, 1024) 43 | _, err := conn.Read(buf) 44 | if err != nil { 45 | logWithTimestamp("Connection lost.") 46 | return 47 | } 48 | logWithTimestamp("Received ACK from client.") 49 | 50 | // Randomly simulate packet loss 51 | if simulateLoss(lossThreshold) { 52 | logWithTimestamp("Packet loss detected! Reducing congestion window.") 53 | cwnd = max(1, cwnd/2) // On loss, halve the cwnd 54 | } else { 55 | // On success, increase cwnd exponentially until maxCwnd 56 | if cwnd < maxCwnd { 57 | cwnd *= 2 58 | if cwnd > maxCwnd { 59 | cwnd = maxCwnd 60 | } 61 | } 62 | logWithTimestamp(fmt.Sprintf("ACK successful, increasing cwnd. New cwnd = %d", cwnd)) 63 | } 64 | 65 | logWithTimestamp("---- End of RTT cycle ----") 66 | } 67 | } 68 | 69 | func simulateLoss(threshold float64) bool { 70 | return float64(time.Now().UnixNano()%100) < threshold*100 71 | } 72 | 73 | func max(a, b int) int { 74 | if a > b { 75 | return a 76 | } 77 | return b 78 | } 79 | 80 | func main() { 81 | listener, err := net.Listen("tcp", "localhost:8080") 82 | if err != nil { 83 | logWithTimestamp(fmt.Sprintf("Error starting server: %v", err)) 84 | return 85 | } 86 | defer listener.Close() 87 | 88 | logWithTimestamp("Server is listening on localhost:8080") 89 | 90 | for { 91 | conn, err := listener.Accept() 92 | if err != nil { 93 | logWithTimestamp(fmt.Sprintf("Error accepting connection: %v", err)) 94 | continue 95 | } 96 | go handleConnection(conn) 97 | } 98 | } 99 | 100 | --------------------------------------------------------------------------------