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
203: Working with net.Conn Directly
Imagine you're using an old-school walkie-talkie. When you want to talk to someone, you first have to make sure you're both tuned to the exact same frequency. Once you've established that link, there's no fancy "envelope" or "header" around your words; you just press the button, speak into the mic, and release it. To hear the other person, you just listen to the speaker. You don't know exactly when they'll start talking or how long their sentence will be—you just keep your ear open until you hear something.
That's exactly how net.Conn works in Go. While most of your work probably happens in net/http, that package is actually just a high-level wrapper. Underneath the hood, HTTP is just a specific way of formatting text sent over a net.Conn. When we work with net.Conn directly, we're stripping away the "HTTP" part and dealing with the raw stream of bytes.
Stripping away the HTTP abstraction
In a typical HTTP request, the browser sends a formatted block of text, and the server parses it. But with net.Conn, there is no "request" or "response" object. There is only a stream. You push bytes into one end, and the other person pulls bytes out of their end.
I remember the first time I tried to write a custom TCP protocol; I expected the Read method to wait until the other side finished their entire thought. It doesn't. It returns as soon as any data is available. If the sender sends "Hello World" in two separate packets, your Read might return "Hello " first, and then "World" on the next call. You have to be prepared for that fragmentation.
package main
import (
"fmt"
"net"
"os"
)
func main() {
// We "Dial" to establish the connection (like tuning the walkie-talkie)
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
fmt.Println("Error connecting:", err)
os.Exit(1)
}
defer conn.Close()
// We send raw bytes. No headers, no methods, just data.
message := "PING"
fmt.Fprintf(conn, message)
// We create a buffer to hold the incoming data.
// This is like our "ear" waiting for sound.
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("Error reading:", err)
return
}
fmt.Printf("Received: %s\n", string(buffer[:n]))
}
The Read-Write Loop
Usually, you don't just send one message and quit. You want a conversation. To do this, we typically wrap the Read and Write operations in a loop. Because net.Conn implements the io.Reader and io.Writer interfaces, you can use all the great utility functions in the io package, like io.Copy or bufio.NewScanner.
I highly recommend using bufio.Scanner when you're dealing with text-based protocols. It handles the "fragmentation" problem for you by looking for a specific delimiter (like a newline character) and giving you the full line once it arrives.
// Inside a server's handler function
func handleConnection(conn net.Conn) {
defer conn.Close()
// Using a scanner simplifies the "listening" process significantly
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
text := scanner.Text()
fmt.Printf("Client said: %s\n", text)
// Send a response back
conn.Write([]byte("ACK: " + text + "\n"))
}
}
The Danger of the Infinite Read
Here is a trap I've fallen into more than once: the blocking read. When you call conn.Read(), your goroutine stops dead in its tracks. It will wait there forever if the other side never sends data and never closes the connection. This is how you end up with "leaked" goroutines that eat up your memory.
To prevent this, you should use SetDeadline. This tells Go: "If I don't hear anything for the next 5 seconds, stop waiting and return an error." It's the software equivalent of saying, "Hello? Are you still there?" before hanging up the phone.
import "time"
// ... inside your connection handler
// Set a deadline for the next Read operation
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, err := conn.Read(buffer)
if err != nil {
// This will trigger if the deadline is hit
fmt.Println("Connection timed out!")
}
📋 Practical Task
Build a TCP-based Heartbeat Monitor
Your task is to create a small server and client pair that implements a "Heartbeat" mechanism to ensure a connection is still alive.
- The Server: Should listen on port
9000. It must expect a message saying"HEARTBEAT"every 3 seconds. If the server doesn't receive a message within 5 seconds (useSetReadDeadline), it should print "Client lost: Timeout" and close the connection. - The Client: Should connect to the server and send the string
"HEARTBEAT\n"every 3 seconds using atime.Ticker. - The Goal: Run both. Verify that the server stays active. Then, manually kill the client process and observe the server detecting the timeout and cleaning up the connection.
There are no comments for now.