What Is WebSocket?
You open a live football score page. The score updates every few seconds — but you never clicked refresh. You open a chat app and your friend's message appears instantly. You watch a real-time stock dashboard and the numbers change as trades happen.
None of this is possible with plain HTTP. Every one of those features runs on WebSocket.
The Problem HTTP Can't Solve
HTTP follows a strict rule: the client asks, the server answers, the connection closes. This works perfectly for loading pages and fetching data. But it falls apart for anything real-time.
Before WebSocket, developers hacked around this with two approaches:
Polling
The client asks the server "any updates?" every few seconds — whether there are updates or not.
Client → "Any new messages?" Server → "No."
Client → "Any new messages?" Server → "No."
Client → "Any new messages?" Server → "Yes! Here's one."
Wasteful. Most requests are empty. Latency depends on the poll interval.
Long Polling
The client sends a request and the server holds it open until it has data to send, then responds and the cycle repeats.
Client → "Any updates?"
Server → [holds the connection...]
Server → [holds the connection...]
Server → "Here's data!" → connection closes
Client → "Any updates?" → starts again
Better, but still fundamentally request/response. Every delivery requires reconnecting.
WebSocket's answer: Open one persistent connection. Both sides can send data at any time, in any direction, without asking permission first.
What Is WebSocket?
WebSocket is a communication protocol that provides a persistent, full-duplex channel over a single TCP connection. It was standardized in 2011 as RFC 6455.
Full-duplex means both the client and server can send messages simultaneously and independently — like a phone call, not a walkie-talkie.
Key characteristics:
- Persistent connection — stays open until explicitly closed
- Full-duplex — server and client both push freely, no turn-taking
- Low overhead — no HTTP headers on every message, just a small frame
- Real-time — sub-millisecond latency for live data
- Built on HTTP — starts as an HTTP request, then upgrades
How WebSocket Works: The Upgrade Handshake
WebSocket does not replace HTTP — it starts with HTTP, then upgrades the connection.
Step 1 — The Client Sends an Upgrade Request
GET /chat HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
The key headers are Upgrade: websocket and Connection: Upgrade. They tell the server: "I want to switch protocols."
Step 2 — The Server Accepts
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Status code 101 Switching Protocols — the only time you'll see that number. From this point on, the HTTP handshake is over. The TCP connection stays open and is now a WebSocket channel.
Step 3 — Full-Duplex Communication
Client → "Hello!"
Server → "Hi! 3 users online."
Server → "New message from Bob: 'Hey everyone'" ← server pushes without being asked
Client → "Got it, thanks."
Server → "Bob has left the chat." ← server pushes again
When to Use WebSocket
WebSocket is not a replacement for HTTP. Use the right tool for the right job:
| Use Case | Use HTTP | Use WebSocket |
|---|---|---|
| Fetching user profile | ✓ | |
| Submitting a form | ✓ | |
| Live chat | ✓ | |
| Real-time notifications | ✓ | |
| Live sports scores | ✓ | |
| Stock price dashboards | ✓ | |
| Multiplayer game state | ✓ | |
| Collaborative editing | ✓ | |
| File upload | ✓ | |
| Fetching an API resource | ✓ |
Rule of thumb: If the server needs to push data to the client without the client asking first — use WebSocket. Everything else — use HTTP.
WebSocket in Go
The standard library does not include a WebSocket implementation. The most widely used package is gorilla/websocket.
go get github.com/gorilla/websocket
Building a WebSocket Server
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
// upgrader converts an HTTP connection into a WebSocket connection
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // allow all origins in development
},
}
func handleWS(w http.ResponseWriter, r *http.Request) {
// 1. Upgrade the HTTP connection to WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
fmt.Println("Client connected:", conn.RemoteAddr())
// 2. Read and echo messages in a loop
for {
messageType, msg, err := conn.ReadMessage()
if err != nil {
log.Println("Client disconnected:", err)
break
}
fmt.Printf("Received: %s\n", msg)
// 3. Send a response back to the same client
if err := conn.WriteMessage(messageType, msg); err != nil {
log.Println("Write error:", err)
break
}
}
}
func main() {
http.HandleFunc("/ws", handleWS)
log.Println("WebSocket server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Building a WebSocket Client
package main
import (
"fmt"
"log"
"github.com/gorilla/websocket"
)
func main() {
// 1. Dial the WebSocket server (handles the upgrade handshake automatically)
conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:8080/ws", nil)
if err != nil {
log.Fatal("Connection error:", err)
}
defer conn.Close()
// 2. Send a message to the server
if err := conn.WriteMessage(websocket.TextMessage, []byte("Hello from client!")); err != nil {
log.Fatal("Write error:", err)
}
// 3. Read the server's response
_, msg, err := conn.ReadMessage()
if err != nil {
log.Fatal("Read error:", err)
}
fmt.Printf("Server replied: %s\n", msg)
}
Broadcasting to All Connected Clients (Hub Pattern)
Real-world apps need to send messages to all connected clients — not just echo back to one. The standard pattern in Go is a Hub:
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
// Hub manages all active WebSocket connections
type Hub struct {
clients map[*websocket.Conn]bool
broadcast chan []byte
register chan *websocket.Conn
unregister chan *websocket.Conn
}
func newHub() *Hub {
return &Hub{
clients: make(map[*websocket.Conn]bool),
broadcast: make(chan []byte),
register: make(chan *websocket.Conn),
unregister: make(chan *websocket.Conn),
}
}
// Run listens for events and manages the client list
func (h *Hub) Run() {
for {
select {
case conn := <-h.register:
h.clients[conn] = true
log.Println("Client registered:", conn.RemoteAddr())
case conn := <-h.unregister:
if _, ok := h.clients[conn]; ok {
delete(h.clients, conn)
conn.Close()
log.Println("Client unregistered:", conn.RemoteAddr())
}
case msg := <-h.broadcast:
// Send the message to every connected client
for conn := range h.clients {
if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
h.unregister <- conn
}
}
}
}
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWS(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
hub.register <- conn
defer func() {
hub.unregister <- conn
}()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
hub.broadcast <- msg
}
}
func main() {
hub := newHub()
go hub.Run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
handleWS(hub, w, r)
})
log.Println("Chat server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
HTTP vs WebSocket
| Feature | HTTP | WebSocket |
|---|---|---|
| Connection | Opens and closes per request | Stays open persistently |
| Direction | Client → Server only | Both directions simultaneously |
| Latency | Per-request overhead | Near zero after handshake |
| Overhead | Full headers on every message | Small frame header (~2–10 bytes) |
| Use case | Data fetching, CRUD | Real-time, push, streaming |
| Protocol upgrade | — | Starts as HTTP, upgrades via 101
|
| State | Stateless | Stateful |
Summary
| Concept | What It Is |
|---|---|
| WebSocket | A protocol for persistent, full-duplex TCP communication |
| Full-duplex | Both sides can send messages simultaneously |
| Upgrade handshake | An HTTP 101 exchange that converts the connection |
| Hub pattern | A Go struct that manages all connections and broadcasts |
gorilla/websocket |
The standard Go library for WebSocket servers and clients |
Further Reading & Watch
- 📺 WebSocket Protocol Explained for Beginners
- 📺 Building a Chat App — Intro to WebSockets
- 📺 WebSockets in 100 Seconds — Fireship
- 📖 gorilla/websocket — GitHub
- 📖 RFC 6455 — The WebSocket Protocol
- 📖 MDN — WebSockets API
You now understand the full communication toolkit of the modern web: HTTP for requests, HTTPS for security, REST and GraphQL for structured APIs, and WebSockets for real-time streams. Every chat app, live dashboard, and multiplayer game you've ever used speaks this language.
The next step? Build something with all of it.


Top comments (0)