├── .github └── assets │ └── terminal.gif ├── .gitignore ├── LICENSE ├── README.md ├── tcp-nodelay-go ├── client.go ├── go.mod └── server.go ├── tcpserver-go ├── client.go ├── concurrentServer.go ├── go.mod └── server.go ├── tcpserver-nodejs └── index.mjs ├── udpserver-go ├── client.go ├── go.mod └── server.go └── udpserver-nodejs └── index.mjs /.github/assets/terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrenbrandao/udemy-networking-fundamentals/aaf2aebaff0ae526a5e78e7e11e56ab1d1fd7bf0/.github/assets/terminal.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 André Brandão 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fundamentals of Networking for Effective Backend Design 2 | 3 | Exercises and code from Hussein Nasser's Course [Fundamentals of Networking for Effective Backend Design](https://www.udemy.com/course/fundamentals-of-networking-for-effective-backend-design). 4 | 5 | ## How to Run 6 | 7 | Install the [latest Go version](https://go.dev/) on your computer if it is not already installed. 8 | Also, if running the Nodejs servers, install the [LTS Version](https://nodejs.org/en/). 9 | 10 | Then, go to the `udpserver` or `tcpserver` respective directories and execute the following command: 11 | 12 | Go: 13 | 14 | ```bash 15 | go run main.go 16 | ``` 17 | 18 | Nodejs: 19 | 20 | ```bash 21 | node index.mjs 22 | ``` 23 | 24 | In your terminal, use the Netcat utility to make the requests. 25 | 26 | ```bash 27 | # TCP 28 | nc -t 127.0.0.1 5500 29 | 30 | # UDP 31 | nc -u 127.0.0.1 5500 32 | ``` 33 | 34 | ![Terminal Commands](.github/assets/terminal.gif) 35 | 36 | Note: I have improved the TCP and UDP servers in Go after reading [this article](https://www.linode.com/docs/guides/developing-udp-and-tcp-clients-and-servers-in-go/) from Linode. 37 | 38 | ## Nagle's Algorithm 39 | 40 | This algorithm can affect network performance and to understand it better [this article](https://blog.gopheracademy.com/advent-2019/control-packetflow-tcp-nodelay/) explains the problems with it. 41 | 42 | Run `tcpdump` to observe the behavior. 43 | 44 | ```bash 45 | sudo tcpdump -X -i lo0 'port 8000' 46 | ``` 47 | 48 | Go to the `tcp-nodelay-go` directory and execute the client and servers. 49 | 50 | ```bash 51 | go run client.go 52 | go run server.go 53 | ``` 54 | 55 | Then, disable `TCP_NODELAY` to see it in action in the `client.go` file. 56 | 57 | ```go 58 | conn.SetNoDelay(false) // Disable TCP_NODELAY; Nagle's Algorithm takes action. 59 | ``` 60 | 61 | It is also important to know the Delayed Acknowledgement algorithm, because when it works together with Nagle's Algorithm, we may have 400ms delays. The `TCP_QUICKACK` is the socket option to disable it. [Read more](https://www.extrahop.com/company/blog/2016/tcp-nodelay-nagle-quickack-best-practices/). 62 | 63 | ## Extras 64 | 65 | - Read how to [Build a TCP Connection Pool From Scratch With Go](https://betterprogramming.pub/build-a-tcp-connection-pool-from-scratch-with-go-d7747023fe14) 66 | 67 | ## License 68 | 69 | [MIT](LICENSE) © André Brandão 70 | -------------------------------------------------------------------------------- /tcp-nodelay-go/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | ) 8 | 9 | func main() { 10 | target := "localhost:8000" 11 | 12 | raddr, err := net.ResolveTCPAddr("tcp", target) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | // Establish a connection with the server. 18 | conn, err := net.DialTCP("tcp", nil, raddr) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | // conn.SetNoDelay(false) // Disable TCP_NODELAY; Nagle's Algorithm takes action. 24 | 25 | fmt.Println("Sending Gophers down the pipe...") 26 | 27 | for i := 0; i < 5; i++ { 28 | // Send the word "GOPHER" to the open connection. 29 | _, err = conn.Write([]byte("GOPHER\n")) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tcp-nodelay-go/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /tcp-nodelay-go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "net" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | port := ":" + "8000" 13 | 14 | // Create a listening socket. 15 | l, err := net.Listen("tcp", port) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer l.Close() 20 | 21 | for { 22 | // Accept new connections. 23 | c, err := l.Accept() 24 | if err != nil { 25 | log.Println(err) 26 | return 27 | } 28 | 29 | // Process newly accepted connection. 30 | go handleConnection(c) 31 | } 32 | } 33 | func handleConnection(c net.Conn) { 34 | fmt.Printf("Serving %s\n", c.RemoteAddr().String()) 35 | 36 | for { 37 | // Read what has been sent from the client. 38 | netData, err := bufio.NewReader(c).ReadString('\n') 39 | if err != nil { 40 | log.Println(err) 41 | return 42 | } 43 | 44 | cdata := strings.TrimSpace(netData) 45 | if cdata == "GOPHER" { 46 | c.Write([]byte("GopherAcademy Advent 2019!")) 47 | } 48 | 49 | if cdata == "EXIT" { 50 | break 51 | } 52 | } 53 | c.Close() 54 | } 55 | -------------------------------------------------------------------------------- /tcpserver-go/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | arguments := os.Args 13 | if len(arguments) == 1 { 14 | fmt.Println("Please provide host:port.") 15 | return 16 | } 17 | 18 | CONNECT := arguments[1] 19 | c, err := net.Dial("tcp", CONNECT) 20 | if err != nil { 21 | fmt.Println(err) 22 | return 23 | } 24 | 25 | for { 26 | reader := bufio.NewReader(os.Stdin) 27 | fmt.Print(">> ") 28 | text, _ := reader.ReadString('\n') 29 | fmt.Fprintf(c, text+"\n") 30 | 31 | message, _ := bufio.NewReader(c).ReadString('\n') 32 | fmt.Print("->: " + message) 33 | if strings.TrimSpace(string(text)) == "exit" { 34 | fmt.Println("TCP client exiting...") 35 | c.Close() 36 | return 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tcpserver-go/concurrentServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | tcpAddr := net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5500} 12 | ln, err := net.ListenTCP("tcp", &tcpAddr) 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | count := 0 19 | 20 | for { 21 | conn, err := ln.Accept() 22 | if err != nil { 23 | panic(err) 24 | } 25 | count++ 26 | 27 | go handleConnection(conn, count) 28 | } 29 | } 30 | 31 | func handleConnection(conn net.Conn, count int) { 32 | fmt.Println(fmt.Sprintf("TCP Handshake Successful with: %s", conn.RemoteAddr().String())) 33 | conn.Write([]byte(fmt.Sprintf("Connection number %d\n", count))) 34 | 35 | msgCount := 1 36 | 37 | for { 38 | s, err := bufio.NewReader(conn).ReadString('\n') 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | if strings.TrimSpace(s) == "exit" { 45 | fmt.Println(fmt.Sprintf("Closing connection number %d", count)) 46 | conn.Close() 47 | return 48 | } 49 | 50 | fmt.Println(fmt.Sprintf("Message received: '%s', from address %s", strings.TrimSuffix(s, "\n"), conn.RemoteAddr().String())) 51 | conn.Write([]byte(fmt.Sprintf("Message received number %d\n", msgCount))) 52 | msgCount++ 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tcpserver-go/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /tcpserver-go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | tcpAddr := net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5500} 12 | ln, err := net.ListenTCP("tcp", &tcpAddr) 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | conn, err := ln.Accept() 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | fmt.Println(fmt.Sprintf("TCP Handshake Successful with: %s", conn.RemoteAddr().String())) 24 | 25 | count := 0 26 | 27 | for { 28 | s, err := bufio.NewReader(conn).ReadString('\n') 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | if strings.TrimSpace(s) == "exit" { 35 | fmt.Println("Closing connection...") 36 | conn.Close() 37 | break 38 | } 39 | 40 | fmt.Println(fmt.Sprintf("Message received: '%s', from address %s", strings.TrimSuffix(s, "\n"), conn.RemoteAddr().String())) 41 | conn.Write([]byte(fmt.Sprintf("Message received number %d\n", count))) 42 | count += 1 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tcpserver-nodejs/index.mjs: -------------------------------------------------------------------------------- 1 | import net from 'net'; 2 | 3 | const server = net.createServer(socket => { 4 | console.log(`TCP Handshake successful with ${socket.remoteAddress}:${socket.remotePort}`) 5 | 6 | socket.write('Hello client!\n') 7 | socket.on('data', data => { 8 | console.log(`Received data: ${data.toString()}`) 9 | }) 10 | }) 11 | 12 | server.listen(5500, '127.0.0.1') -------------------------------------------------------------------------------- /udpserver-go/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | arguments := os.Args 13 | if len(arguments) == 1 { 14 | fmt.Println("Please provide a host:port string") 15 | return 16 | } 17 | CONNECT := arguments[1] 18 | 19 | s, err := net.ResolveUDPAddr("udp4", CONNECT) 20 | if err != nil { 21 | fmt.Println(err) 22 | return 23 | } 24 | 25 | c, err := net.DialUDP("udp4", nil, s) 26 | if err != nil { 27 | fmt.Println(err) 28 | return 29 | } 30 | 31 | fmt.Printf("The UDP server is %s\n", c.RemoteAddr().String()) 32 | defer c.Close() 33 | 34 | for { 35 | reader := bufio.NewReader(os.Stdin) 36 | fmt.Print(">> ") 37 | text, _ := reader.ReadString('\n') 38 | data := []byte(text + "\n") 39 | _, err = c.Write(data) 40 | if strings.TrimSpace(string(data)) == "exit" { 41 | fmt.Println("Exiting UDP client!") 42 | return 43 | } 44 | 45 | if err != nil { 46 | fmt.Println(err) 47 | return 48 | } 49 | 50 | buffer := make([]byte, 1024) 51 | n, _, err := c.ReadFromUDP(buffer) 52 | if err != nil { 53 | fmt.Println(err) 54 | return 55 | } 56 | fmt.Printf("Reply: %s\n", string(buffer[0:n])) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /udpserver-go/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /udpserver-go/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | udpAddr := net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5500} 11 | ln, err := net.ListenUDP("udp4", &udpAddr) 12 | 13 | defer ln.Close() 14 | 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | buf := make([]byte, 1024) 20 | count := 0 21 | 22 | for { 23 | n, addr, err := ln.ReadFromUDP(buf) 24 | 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | if strings.TrimSpace(string(buf[:n-1])) == "exit" { 30 | fmt.Println("Exiting UDP server...") 31 | return 32 | } 33 | 34 | fmt.Println(fmt.Sprintf("Message received: %s, from address %s:%d", buf[:n-2], addr.IP.String(), addr.Port)) 35 | 36 | data := []byte(fmt.Sprintf("Request number %d to this server", count)) 37 | count += 1 38 | _, err = ln.WriteToUDP(data, addr) 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /udpserver-nodejs/index.mjs: -------------------------------------------------------------------------------- 1 | import dgram from 'dgram'; 2 | 3 | const socket = dgram.createSocket("udp4") 4 | socket.bind(5500, "127.0.0.1") 5 | socket.on("message", (msg, info) => { 6 | console.log(`My server got a datagram ${msg}, from: ${info.address}:${info.port}`) 7 | }) 8 | 9 | --------------------------------------------------------------------------------