Writing a Network Server in Go: From TCP to HTTP in One Evening

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Go is the Ideal Language for Network Programming?

\n\n

If you've ever written a network server in Python (especially using socket or asyncio), you know how quickly code can turn into a maze of callbacks and event loops. Go (Golang) solves this problem at the architectural level: goroutines and channels make asynchronous network work as simple as writing sequential code.

\n\n

In this article, we'll explore network programming in Go in practice. You'll learn how to build a full-fledged TCP chat in 15 minutes, send a UDP packet, and launch an HTTP server with routing. All examples are working and can be copied and run.

\n\n

Basics: The net Package and Its Superpower

\n\n

In Go, all the network magic lives in the standard net package. It provides a unified interface for working with TCP, UDP, Unix sockets, and even SSL/TLS. The main feature is that the Listen, Dial, and Accept functions work synchronously but don't block the entire program because each goroutine is lightweight.

\n\n

Let's get straight to the point.

\n\n

TCP Server: A Chat in 10 Lines

\n\n

Let's create a simple echo server that accepts connections and sends back what it received. This is a classic of network programming in Go.

\n\n
package main\n\nimport (\n    "bufio"\n    "fmt"\n    "net"\n)\n\nfunc handleConnection(conn net.Conn) {\n    defer conn.Close()\n    scanner := bufio.NewScanner(conn)\n    for scanner.Scan() {\n        message := scanner.Text()\n        fmt.Printf("Received: %s\\n", message)\n        conn.Write([]byte("Echo: " + message + "\\n"))\n    }\n}\n\nfunc main() {\n    listener, _ := net.Listen("tcp", ":8080")\n    fmt.Println("TCP server started on :8080")\n    for {\n        conn, _ := listener.Accept()\n        go handleConnection(conn) // each goroutine is a separate client\n    }\n}
\n\n

What's happening here?

\n
    \n
  • net.Listen("tcp", ":8080") — creates a TCP listener on port 8080.
  • \n
  • The infinite for loop accepts new connections via Accept().
  • \n
  • Each connection is handled in a separate goroutine go handleConnection().
  • \n
  • Inside the function, we read lines using bufio.Scanner and send a response.
  • \n
\n\n

Run this code, open a terminal, and connect via nc localhost 8080 (or Putty). Any message will be returned with the prefix "Echo:". Surprisingly, this is already a full-fledged network server capable of serving thousands of clients without a single callback.

\n\n

UDP: Fast Delivery Without Guarantees

\n\n

If TCP is like a phone call (established a connection — we talk), then UDP is like postcards: sent and forgotten. Go makes working with UDP just as simple.

\n\n

UDP Client and Server

\n\n
// Server\npackage main\n\nimport (\n    "fmt"\n    "net"\n)\n\nfunc main() {\n    addr, _ := net.ResolveUDPAddr("udp", ":8081")\n    conn, _ := net.ListenUDP("udp", addr)\n    defer conn.Close()\n\n    buffer := make([]byte, 1024)\n    for {\n        n, clientAddr, _ := conn.ReadFromUDP(buffer)\n        message := string(buffer[:n])\n        fmt.Printf("Received from %s: %s\\n", clientAddr, message)\n        conn.WriteToUDP([]byte("Acknowledged: "+message+"\\n"), clientAddr)\n    }\n}\n\n// Client\n// In a separate file or goroutine:\nconn, _ := net.Dial("udp", "localhost:8081")\ndefer conn.Close()\nconn.Write([]byte("Hello, UDP!"))\nbuffer := make([]byte, 1024)\nn, _ := conn.Read(buffer)\nfmt.Println(string(buffer[:n]))
\n\n

Key difference from TCP:

\n