Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
201: TCP and UDP with the net Package
I want to show you a snippet of code that looks perfectly fine at first glance. A lot of developers coming to Go from higher-level languages make this exact mistake when they first touch the net package. They treat a TCP connection like a series of discrete messages, when in reality, it's just a continuous stream of bytes.
// A simple TCP server that reads a message and prints it
listener, _ := net.Listen("tcp", ":8080")
conn, _ := listener.Accept()
defer conn.Close()
buf := make([]byte, 1024)
n, err := conn.Read(buf) // The mistake is right here
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received: %s\n", string(buf[:n]))
The 'Missing Data' Mystery
The bug here isn't a syntax error—it's a conceptual one. If you run this and send a small string like "Hello", it works. But if you send a large JSON payload or a long string, you might find that your server only prints the first few hundred bytes and then stops. Or, if the network is jittery, the data might arrive in two separate chunks, and your server will exit after the first chunk, leaving the rest of the message hanging in the buffer.
TCP does not have "messages." It has a stream. When you call conn.Read(buf), you are essentially saying, "Give me whatever is available in the buffer right now, up to 1024 bytes." You have no guarantee that one Read call equals one Write call from the client.
Treating TCP as a Stream with bufio
To fix this, you need a way to tell the server when a message actually ends. This is called "framing." The easiest way to do this in Go is by using a delimiter (like a newline character) and the bufio package, which handles the buffering for you so you don't have to manage byte slices manually.
reader := bufio.NewReader(conn)
// Keep reading until we hit a newline character
message, err := reader.ReadString('\n')
if err != nil {
log.Printf("Error reading: %v", err)
}
fmt.Printf("Received full message: %s", message)
By using ReadString('\n'), Go will keep calling Read under the hood until it encounters that specific byte. Now, it doesn't matter if the network splits your message into ten pieces; your code will wait until the complete "frame" has arrived. In a real production system, you'd probably use a length-prefix (sending the number of bytes first), but for most tools, delimiters are the way to go.
The Connectionless Nature of UDP
Now, UDP is a completely different animal. While TCP is like a phone call (you establish a connection, you know the other person is there, and data arrives in order), UDP is like sending postcards. You just throw the data at an address and hope for the best.
In Go, you don't "Accept" a UDP connection because there is no connection. You just listen for packets. I've found that people often get confused trying to use net.Dial with UDP. While you can do it to "lock" a socket to a specific address, the more idiomatic way for a server is ListenUDP.
addr, _ := net.ResolveUDPAddr("udp", ":8081")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()
buf := make([]byte, 1024)
// ReadFromUDP gives us the data AND the address of who sent it
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received %d bytes from %s: %s\n", n, remoteAddr, string(buf[:n]))
Notice the difference: ReadFromUDP gives you the remoteAddr. Since there's no persistent connection, you need that address if you want to send a response back. Also, notice that for UDP, the "packet" is preserved. If the client sends 50 bytes, ReadFromUDP will generally give you those 50 bytes in one go, or nothing at all.
- Use TCP when you need reliability, ordering, and a persistent session (e.g., a database connection, an API).
- Use UDP when speed is more important than 100% delivery, or when you're dealing with many small, independent updates (e.g., gaming state, DNS, heartbeats).
📋 Practical Task
Build a UDP Heartbeat Monitor
Your task is to create a simple UDP-based monitoring system. You will need to write two small programs: a Heartbeat Client and a Monitor Server.
The Heartbeat Client:
- Should send a UDP packet containing the string "ALIVE" to
localhost:9000every 2 seconds. - It should run in an infinite loop.
The Monitor Server:
- Should listen on UDP port
9000. - Every time it receives a packet, it should print the sender's IP address and the current timestamp to the console.
- It must be able to handle packets from multiple different clients simultaneously.
Requirements:
- Use
net.ListenUDPfor the server. - Use
net.DialUDPornet.WriteToUDPfor the client. - Ensure the server doesn't crash if it receives a packet larger than its buffer.
There are no comments for now.