What I Learned About TCP by Building My Own Server in Go

What I Learned About TCP by Building My Own Server in Go

Recently, I decided to dive a little deeper into how the internet works, not just using APIs or libraries, but actually writing something that communicates over the network at a lower level. So I built a basic TCP server in Go.

GoTCPNetworkingBackend

Recently, I decided to dive a little deeper into how the internet works not just using APIs or libraries, but actually writing something that communicates over the network at a lower level. So I built a basic TCP server in Go, using nothing but the standard library.

This post is a breakdown of what I built, what I learned, and why it helped me better understand how TCP actually works.


Why TCP?

Almost everything we do online relies on TCP web browsing, messaging, downloads. But in most higher level languages or frameworks, it's abstracted away. I wanted to see what it really takes to send and receive raw data between two machines.


The Basic TCP Server in Go

Here's a simple server that listens for connections and echoes back any messages it receives.

Go
package main
 
import (
    "bufio"
    "fmt"
    "net"
)
 
func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer listener.Close()
 
    fmt.Println("Server listening on port 8080...")
 
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
 
        go handleConnection(conn)
    }
}
 
func handleConnection(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
 
    for {
        message, err := reader.ReadString('\n')
        if err != nil {
            return
        }
 
        fmt.Printf("Received: %s", message)
        conn.Write([]byte("Echo: " + message))
    }
}

You can connect to it with telnet localhost 8080, type a message, and see it echoed back.


Key Lessons Learned

1. TCP is Stream-Based

TCP doesn't send discrete "messages" it sends a continuous stream of bytes. It's up to you to define where one message ends and another begins. That's why the server reads until it finds a newline character (\n), to mark the end of a message.


2. Concurrency is Essential

Each new client connection gets its own goroutine. Without concurrency, only one client could talk to the server at a time, and others would be blocked until it disconnected.


3. Error Handling is Critical

Ignoring errors, even temporarily, causes real problems. If you forget to close connections or miss read/write errors, you'll end up with leaking resources and stuck processes. TCP might be reliable, but your code still needs to be defensive.


4. Clients Can Disconnect at Any Time

Connections close unexpectedly, sometimes without warning. The server needs to be prepared for an EOF error or a broken pipe. Handling that gracefully is key to writing reliable networked applications.


5. You Can Build Protocols on Top of TCP

Once I had the basic echo functionality working, I realized I could start designing commands like PING, HELLO, or LOGIN. That opens the door to creating a simple text-based protocol something like a lightweight Redis or custom chat server.


Next Steps

This small project led to a bunch of ideas I want to experiment with:

  • Add a basic protocol with commands
  • Handle user sessions and client identification
  • Build a chat room or multiplayer game server
  • Add TLS support for secure communication

Final Thoughts

Writing a TCP server from scratch helped me understand the foundations of client-server communication. It made everything above the TCP layer like HTTP, WebSockets, and even frameworks make more sense.

If you're learning Go or just want to get a better grasp of how the internet actually works, building a TCP server is a great project to try.