🌐 网络与服务器基础
深入理解 TCP/IP 协议栈、HTTP 协议演进、WebSocket 实时通信——这是服务器开发的地基。
1. TCP/IP 协议栈
互联网通信建立在分层协议栈上。理解每一层的职责,是理解服务器工作原理的前提。
TCP 三次握手与四次挥手
用 Go 演示 TCP 服务器
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
func main() {
// 监听 TCP 端口 8080
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
fmt.Println("TCP 服务器启动,监听 :8080")
for {
// 阻塞等待客户端连接
conn, err := listener.Accept()
if err != nil {
continue
}
// 每个连接开一个 goroutine 处理 (并发核心)
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
fmt.Printf("新连接: %s\n", remote)
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Text()
fmt.Printf("[%s] 收到: %s\n", remote, line)
// Echo: 回显数据
response := strings.ToUpper(line) + "\n"
conn.Write([]byte(response))
}
fmt.Printf("连接断开: %s\n", remote)
}
2. HTTP 协议深度解析
HTTP 版本演进
| 版本 | 年份 | 核心特性 | 问题 |
|---|---|---|---|
| HTTP/1.0 | 1996 | 短连接,每次请求新建TCP | 性能差,连接开销大 |
| HTTP/1.1 | 1997 | 持久连接 Keep-Alive,管道化 | 队头阻塞 (HOL blocking) |
| HTTP/2 | 2015 | 多路复用、头部压缩、服务器推送 | TCP 层队头阻塞 |
| HTTP/3 | 2022 | 基于 QUIC/UDP,彻底解决队头阻塞 | 普及率还在提升 |
HTTP 请求结构
Content-Type: application/json
Authorization: Bearer eyJhbGc...
Content-Length: 45
HTTP 状态码速查
2xx 成功
200 OK— 请求成功201 Created— 资源已创建204 No Content— 成功,无返回体206 Partial Content— 范围请求成功
4xx 客户端错误
400 Bad Request— 请求格式错误401 Unauthorized— 未认证403 Forbidden— 无权限404 Not Found— 资源不存在429 Too Many Requests— 限流
5xx 服务器错误
500 Internal Server Error— 服务器异常502 Bad Gateway— 网关错误503 Service Unavailable— 服务不可用504 Gateway Timeout— 网关超时
3xx 重定向
301 Moved Permanently— 永久重定向302 Found— 临时重定向304 Not Modified— 缓存未修改307 Temporary Redirect— 临时(保持方法)
用 Go 标准库实现 HTTP 服务器
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type Response struct {
Status string `json:"status"`
Message string `json:"message"`
Time string `json:"time"`
}
func main() {
mux := http.NewServeMux()
// 路由注册
mux.HandleFunc("GET /", homeHandler)
mux.HandleFunc("GET /health", healthHandler)
mux.HandleFunc("POST /echo", echoHandler)
// 服务器配置 (生产环境必须设置超时)
server := &http.Server{
Addr: ":8080",
Handler: loggingMiddleware(mux),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}
fmt.Println("HTTP 服务器启动: http://localhost:8080")
log.Fatal(server.ListenAndServe())
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Status: "ok",
Message: "Welcome to Go Server!",
Time: time.Now().Format(time.RFC3339),
})
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
}
func echoHandler(w http.ResponseWriter, r *http.Request) {
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(body)
}
// 中间件:请求日志
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s %v", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
})
}
3. WebSocket 实时通信
WebSocket 在 HTTP 握手后升级为全双工 TCP 连接,服务器可主动推送数据,适合聊天室、实时通知、在线协作等场景。
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// 生产环境应验证 Origin
CheckOrigin: func(r *http.Request) bool { return true },
}
// Hub 管理所有 WebSocket 连接
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, 256),
register: make(chan *websocket.Conn),
unregister: make(chan *websocket.Conn),
}
}
func (h *Hub) run() {
for {
select {
case conn := <-h.register:
h.clients[conn] = true
fmt.Printf("客户端连接,当前连接数: %d\n", len(h.clients))
case conn := <-h.unregister:
if _, ok := h.clients[conn]; ok {
delete(h.clients, conn)
conn.Close()
fmt.Printf("客户端断开,当前连接数: %d\n", len(h.clients))
}
case message := <-h.broadcast:
// 广播给所有客户端
for conn := range h.clients {
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
h.unregister <- conn
}
}
}
}
}
func wsHandler(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("WebSocket 升级失败:", err)
return
}
hub.register <- conn
// 读取消息并广播
for {
_, msg, err := conn.ReadMessage()
if err != nil {
hub.unregister <- conn
break
}
hub.broadcast <- msg
}
}
func main() {
hub := newHub()
go hub.run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
wsHandler(hub, w, r)
})
log.Println("WebSocket 服务器: ws://localhost:8080/ws")
log.Fatal(http.ListenAndServe(":8080", nil))
}
4. DNS 与服务发现
package main
import (
"context"
"fmt"
"net"
"time"
)
func dnsLookup(host string) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{Timeout: 3 * time.Second}
// 使用 Google Public DNS
return d.DialContext(ctx, "udp", "8.8.8.8:53")
},
}
// 查询 A 记录 (IPv4)
ips, err := resolver.LookupHost(ctx, host)
if err != nil {
fmt.Printf("DNS 查询失败: %v\n", err)
return
}
fmt.Printf("主机 %s 的 IP 地址:\n", host)
for _, ip := range ips {
fmt.Printf(" → %s\n", ip)
}
// 查询 MX 记录 (邮件)
mxs, _ := resolver.LookupMX(ctx, host)
for _, mx := range mxs {
fmt.Printf("MX: %s (优先级: %d)\n", mx.Host, mx.Pref)
}
}
func main() {
dnsLookup("github.com")
}
5. TLS/HTTPS 原理
TLS(Transport Layer Security)在 TCP 之上提供加密、认证和完整性保护。
TLS 1.3 握手只需 1-RTT
相比 TLS 1.2 的 2-RTT,TLS 1.3 合并了握手步骤,显著降低延迟。对于已知服务器还支持 0-RTT 恢复。
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "HTTPS 连接成功! 协议: %s", r.Proto)
})
// TLS 配置 (推荐配置)
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS13, // 最低 TLS 1.3
CurvePreferences: []tls.CurveID{
tls.X25519, // 首选现代曲线
tls.CurveP256,
},
CipherSuites: []uint16{ // TLS 1.3 自动选择,1.2 手动指定
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
},
}
server := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: tlsConfig,
}
// 证书可通过 Let's Encrypt 自动获取
log.Fatal(server.ListenAndServeTLS("cert.pem", "key.pem"))
}
// 自动 HTTPS (使用 autocert)
// import "golang.org/x/crypto/acme/autocert"
// m := &autocert.Manager{
// Cache: autocert.DirCache("certs"),
// Prompt: autocert.AcceptTOS,
// HostPolicy: autocert.HostWhitelist("example.com"),
// }
// server.TLSConfig = m.TLSConfig()
开发环境证书
使用 mkcert 工具快速生成本地可信证书:mkcert -install && mkcert localhost 127.0.0.1
6. 反向代理与负载均衡
:8081:8082:8083package main
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"sync/atomic"
)
// 简易轮询负载均衡器
type LoadBalancer struct {
backends []*url.URL
counter atomic.Uint64
}
func NewLoadBalancer(backends []string) *LoadBalancer {
lb := &LoadBalancer{}
for _, b := range backends {
u, _ := url.Parse(b)
lb.backends = append(lb.backends, u)
}
return lb
}
func (lb *LoadBalancer) next() *url.URL {
idx := lb.counter.Add(1) % uint64(len(lb.backends))
return lb.backends[idx]
}
func (lb *LoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
target := lb.next()
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, "backend unavailable", http.StatusBadGateway)
}
proxy.ServeHTTP(w, r)
fmt.Printf("请求转发到: %s\n", target.Host)
}
func main() {
lb := NewLoadBalancer([]string{
"http://localhost:8081",
"http://localhost:8082",
"http://localhost:8083",
})
log.Fatal(http.ListenAndServe(":80", lb))
}
7. Go 1.22 标准库 net/http 路由增强
在 Go 1.22 之前,net/http.ServeMux 无法区分 HTTP 方法,也不支持路径参数,因此生产项目必须引入 Gorilla Mux、chi 等第三方路由库。Go 1.22 对 ServeMux 进行了重要增强,使其足以满足大多数中小型项目的路由需求,减少依赖。
- 方法限定(Method Routing):路由模式支持
METHOD /path格式。不匹配方法时自动返回 405。不写方法则匹配所有方法(向后兼容)。 - 通配符参数(Wildcard Segments):
{name}匹配单个路径段(不含/),通过r.PathValue("name")取值。 - 尾部通配符(Tail Wildcard):
{path...}贪婪匹配路径末尾的多个段,包含/。 - 精确匹配(Exact Match):路径末尾加
{$}表示精确匹配,不接受额外路径。
package main
import (
"encoding/json"
"log/slog"
"net/http"
"os"
"strconv"
"time"
)
// ─── Go 1.22 ServeMux 完整路由示例 ───────────────────────────
func setupRouter() http.Handler {
mux := http.NewServeMux()
// ① 精确匹配根路径(不匹配 /foo)
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"service": "api", "version": "v1"})
})
// ② 方法限定 + 路径参数
mux.HandleFunc("GET /api/v1/users", listUsersHandler)
mux.HandleFunc("POST /api/v1/users", createUserHandler)
mux.HandleFunc("GET /api/v1/users/{id}", getUserHandler) // {id} 单段
mux.HandleFunc("PUT /api/v1/users/{id}", updateUserHandler)
mux.HandleFunc("DELETE /api/v1/users/{id}", deleteUserHandler)
// ③ 嵌套资源
mux.HandleFunc("GET /api/v1/users/{uid}/posts/{pid}", func(w http.ResponseWriter, r *http.Request) {
uid := r.PathValue("uid")
pid := r.PathValue("pid")
json.NewEncoder(w).Encode(map[string]string{"user_id": uid, "post_id": pid})
})
// ④ 尾部通配符:文件服务
mux.HandleFunc("GET /static/{path...}", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./public/"+r.PathValue("path"))
})
// ⑤ 主机限定(host/path 格式)
// mux.HandleFunc("admin.example.com/dashboard", dashboardHandler)
return mux
}
func getUserHandler(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id") // 提取路径参数
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, `{"error":"invalid id"}`, http.StatusBadRequest)
return
}
// 模拟查询
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"id": id, "name": "Alice"})
}
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
handler := loggingMiddleware2(logger)(setupRouter())
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
logger.Info("服务器启动", "addr", server.Addr)
if err := server.ListenAndServe(); err != nil {
logger.Error("启动失败", "err", err)
os.Exit(1)
}
}
func loggingMiddleware2(logger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
logger.Info("http",
"method", r.Method,
"path", r.URL.Path,
"latency", time.Since(start).String(),
"remote", r.RemoteAddr,
)
})
}
}
// 占位函数
func listUsersHandler(w http.ResponseWriter, r *http.Request) {}
func createUserHandler(w http.ResponseWriter, r *http.Request) {}
func updateUserHandler(w http.ResponseWriter, r *http.Request) {}
func deleteUserHandler(w http.ResponseWriter, r *http.Request) {}
何时仍需第三方路由库?
Go 1.22 ServeMux 不支持:路由中间件分组(需对每个路由单独包装)、正则表达式路径、路由优先级控制(当两个模式有歧义时)。如果你需要这些特性,chi(轻量、标准库兼容)或 Gin(功能最全)仍是更好的选择。对于 CRUD API 服务,标准库已经足够。
8. WebSocket 实时通信
原理:WebSocket 是建立在 TCP 之上的全双工通信协议。连接过程分两阶段:① HTTP 升级握手(客户端发送 Upgrade: websocket 头,服务端回复 101 Switching Protocols);② 建立 WebSocket 帧通信(二进制帧协议,比 HTTP 开销小得多)。
- 全双工(Full-Duplex):服务端和客户端可以同时独立发送数据,不需要请求-响应模型。
- 帧(Frame):WebSocket 数据以帧为单位传输,帧头最小 2 字节。支持文本帧(UTF-8)和二进制帧。
- 心跳(Ping/Pong):协议内置 Ping/Pong 帧,用于检测连接活跃性,防止 NAT 超时。
- 关闭握手(Closing Handshake):一方发送 Close 帧,另一方回复 Close 帧,然后关闭 TCP 连接(类似 TCP 四次挥手)。
package main
import (
"encoding/json"
"log"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket" // go get github.com/gorilla/websocket
)
// ─── 消息类型 ─────────────────────────────────────────────────
type WSMessage struct {
Type string `json:"type"`
Payload any `json:"payload"`
From string `json:"from,omitempty"`
}
// ─── 连接管理器(广播中心) ───────────────────────────────────
type Hub struct {
clients map[*Client]bool
broadcast chan []byte // 广播消息队列
register chan *Client // 新连接
unregister chan *Client // 断开连接
mu sync.RWMutex
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte // 发送队列(缓冲)
id string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 生产环境需要检查 Origin
},
}
func NewHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte, 256),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
// Hub 主循环:管理连接与广播
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.mu.Lock()
h.clients[client] = true
h.mu.Unlock()
log.Printf("客户端 %s 连接,当前 %d 个连接\n", client.id, len(h.clients))
case client := <-h.unregister:
h.mu.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
h.mu.Unlock()
case message := <-h.broadcast:
h.mu.RLock()
for client := range h.clients {
select {
case client.send <- message: // 非阻塞发送
default:
// 发送队列满:断开慢消费者
close(client.send)
delete(h.clients, client)
}
}
h.mu.RUnlock()
}
}
}
// 读协程:处理客户端消息
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
c.conn.SetReadLimit(512 * 1024) // 最大消息 512KB
c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
c.conn.SetPongHandler(func(string) error { // 收到 Pong 更新 deadline
c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
break
}
// 广播给所有连接
c.hub.broadcast <- message
}
}
// 写协程:发送消息给客户端,定时发送 Ping 心跳
func (c *Client) writePump() {
ticker := time.NewTicker(30 * time.Second)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil { return }
w.Write(message)
// 批量发送:将队列中积压的消息一起写出
n := len(c.send)
for i := 0; i < n; i++ {
w.Write([]byte("\n"))
w.Write(<-c.send)
}
if err := w.Close(); err != nil { return }
case <-ticker.C:
// 每 30 秒发送一次 Ping
c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
func serveWS(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("WebSocket 升级失败:", err)
return
}
client := &Client{
hub: hub,
conn: conn,
send: make(chan []byte, 256),
id: r.RemoteAddr,
}
hub.register <- client
go client.writePump() // 每个连接两个 goroutine
go client.readPump()
}
func main() {
hub := NewHub()
go hub.Run()
http.HandleFunc("GET /ws", func(w http.ResponseWriter, r *http.Request) {
serveWS(hub, w, r)
})
http.HandleFunc("POST /broadcast", func(w http.ResponseWriter, r *http.Request) {
var msg WSMessage
json.NewDecoder(r.Body).Decode(&msg)
data, _ := json.Marshal(msg)
hub.broadcast <- data
w.WriteHeader(http.StatusNoContent)
})
log.Println("WebSocket 服务启动 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
WebSocket 生产环境注意事项
① 必须设置 SetReadDeadline 和 SetWriteDeadline,防止慢/死连接永久占用 goroutine。② CheckOrigin 在生产中必须严格验证来源,防止 CSRF。③ 每个 WebSocket 连接需要两个 goroutine(读/写),1 万连接 = 2 万 goroutine ≈ 40MB 栈内存,需要压测评估。④ 负载均衡时需要 sticky session(粘性会话)或使用 Redis Pub/Sub 实现跨节点广播。