网络基础
Go 的标准库在 networking 方面提供了丰富的支持。net 包是所有网络操作的基础,而 net/http 则构建在其之上,提供了完整的 HTTP 客户端和服务端实现。
net/http 概览
net/http 包的核心是 http.Handler 接口,任何实现了 ServeHTTP(ResponseWriter, *Request) 方法的类型都可以作为 HTTP 处理器:
type Handler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}http.HandlerFunc 是一个适配器类型,它将普通的函数转换为 http.Handler:
// HandlerFunc 是一个带有 ServeHTTP 方法的类型
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}http.HandlerFunc 让我们可以直接用函数来处理 HTTP 请求,而不需要显式定义一个结构体并实现接口。这是 Go 中”函数作为一等公民”理念的体现。
net/url —— URL 解析与构建
net/url 包提供了 URL 的解析、构建和查询参数处理功能。
package main
import (
"fmt"
"net/url"
)
func main() {
// 解析 URL
u, err := url.Parse("https://example.com:8080/path?key=value&name=go#section")
if err != nil {
panic(err)
}
fmt.Println(u.Scheme) // https
fmt.Println(u.Host) // example.com:8080
fmt.Println(u.Hostname()) // example.com
fmt.Println(u.Path) // /path
fmt.Println(u.RawQuery) // key=value&name=go
fmt.Println(u.Fragment) // section
// 获取查询参数
fmt.Println(u.Query().Get("key")) // value
fmt.Println(u.Query().Get("name")) // go
// 构建 URL
u2 := &url.URL{
Scheme: "https",
Host: "api.example.com",
Path: "/v1/users",
RawQuery: "page=1&limit=10",
}
fmt.Println(u2.String()) // https://api.example.com/v1/users?page=1&limit=10
// 使用 url.Values 构建查询参数
params := url.Values{}
params.Set("page", "1")
params.Set("limit", "10")
params.Add("tag", "go")
params.Add("tag", "web")
fmt.Println(params.Encode()) // limit=10&page=1&tag=go&tag=web
}net 包 —— TCP/UDP 编程
net 包提供了底层的网络 I/O 操作,包括 TCP 和 UDP 编程。
TCP 服务端
package main
import (
"fmt"
"io"
"net"
)
func main() {
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 {
fmt.Println("接受连接失败:", err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
fmt.Println("读取错误:", err)
}
return
}
fmt.Printf("收到: %s", buf[:n])
conn.Write([]byte("已收到你的消息\n"))
}
}TCP 客户端
package main
import (
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
panic(err)
}
defer conn.Close()
conn.Write([]byte("Hello, TCP Server!\n"))
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
fmt.Printf("服务器响应: %s", buf[:n])
}在实际生产中,很少直接使用 net 包来处理 TCP 连接。通常使用 net/http(HTTP)、gRPC(RPC)或第三方库(如 gorilla/websocket)来处理应用层协议。net 包更适合实现自定义协议。
http.Client —— 发起 HTTP 请求
http.Client 是 Go 中发起 HTTP 请求的主要工具。
基本 GET 请求
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应体: %s\n", string(body[:100]))
}POST 请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
data := map[string]string{
"name": "张三",
"email": "zhangsan@example.com",
}
body, _ := json.Marshal(data)
resp, err := http.Post(
"https://httpbin.org/post",
"application/json",
bytes.NewReader(body),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应: %s\n", string(respBody[:200]))
}自定义 Client
package main
import (
"net/http"
"time"
)
func main() {
// 创建自定义的 http.Client
client := &http.Client{
Timeout: 10 * time.Second, // 请求超时
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个主机最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
},
}
// 使用自定义 client 发起请求
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
panic(err)
}
defer resp.Body.Close()
}http.ServeMux —— 路由器
Go 1.22+ 引入了增强的 http.ServeMux,支持方法匹配和路径参数。
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Go 1.22+ 新语法:方法匹配
mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "获取用户列表")
})
mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "创建用户")
})
// 路径参数(Go 1.22+)
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "获取用户 ID: %s\n", id)
})
fmt.Println("服务器启动 :8080")
http.ListenAndServe(":8080", mux)
}在 Go 1.22 之前,使用的是旧版 ServeMux,它只支持基于路径的匹配,不支持 HTTP 方法匹配。旧版中需要手动检查 r.Method 来区分 GET/POST 等请求方法。
练习题
练习 1
编写一个程序,解析 URL https://user:pass@example.com:443/api/v1/users?page=2&limit=20#results,输出其 scheme、user、host、path、query 参数和 fragment。
解题思路:使用 url.Parse() 解析 URL 字符串,然后访问各个字段。
package main
import (
"fmt"
"net/url"
)
func main() {
rawURL := "https://user:pass@example.com:443/api/v1/users?page=2&limit=20#results"
u, _ := url.Parse(rawURL)
fmt.Println("Scheme:", u.Scheme) // https
fmt.Println("User:", u.User) // user:pass
fmt.Println("Host:", u.Host) // example.com:443
fmt.Println("Hostname:", u.Hostname()) // example.com
fmt.Println("Path:", u.Path) // /api/v1/users
fmt.Println("Fragment:", u.Fragment) // results
fmt.Println("page:", u.Query().Get("page")) // 2
fmt.Println("limit:", u.Query().Get("limit")) // 20
}练习 2
使用 http.Client 编写一个程序,向 https://httpbin.org/uuid 发送 GET 请求,并解析返回的 JSON 中的 uuid 字段。
解题思路:使用 http.Get 发起请求,json.Decoder 直接解码响应体。
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://httpbin.org/uuid")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result struct {
UUID string `json:"uuid"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println("UUID:", result.UUID)
}