5.3. TCP/UDP 编程
5.3.1. TCP 编程
5.3.1.1. TCP 服务器
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 :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))
}
}
5.3.1.2. TCP 客户端
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
panic(err)
}
defer conn.Close()
// 发送数据
conn.Write([]byte("Hello, Server!\n"))
// 接收响应
response := make([]byte, 1024)
n, _ := conn.Read(response)
fmt.Printf("Response: %s", response[:n])
}
5.3.1.3. ⚠️ TCP 陷阱
5.3.1.3.1. 陷阱 1:粘包问题
// ❌ 直接读取,可能读到不完整或多个消息
func handleConn(conn net.Conn) {
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
process(buf[:n]) // 可能是半个消息或多个消息
}
// ✅ 使用长度前缀协议
func handleConn(conn net.Conn) {
reader := bufio.NewReader(conn)
for {
// 读取 4 字节长度
lenBuf := make([]byte, 4)
io.ReadFull(reader, lenBuf)
length := binary.BigEndian.Uint32(lenBuf)
// 读取消息体
data := make([]byte, length)
io.ReadFull(reader, data)
process(data)
}
}
5.3.1.3.2. 陷阱 2:忘记设置超时
// ❌ 无超时,可能永久阻塞
conn, _ := net.Dial("tcp", "server:8080")
conn.Read(buf) // 可能永久阻塞
// ✅ 设置超时
conn, _ := net.DialTimeout("tcp", "server:8080", 5*time.Second)
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
5.3.1.3.3. 陷阱 3:未处理半关闭
// TCP 连接可以半关闭(一端关闭写,但仍可读)
func handleConn(conn net.Conn) {
// 读取客户端数据
data, _ := io.ReadAll(conn)
// 关闭读端,但仍可写
conn.(*net.TCPConn).CloseRead()
// 发送响应
conn.Write([]byte("Response"))
conn.Close()
}
5.3.2. UDP 编程
5.3.2.1. UDP 服务器
func main() {
addr, _ := net.ResolveUDPAddr("udp", ":8080")
conn, err := net.ListenUDP("udp", addr)
if err != nil {
panic(err)
}
defer conn.Close()
buf := make([]byte, 1024)
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
continue
}
fmt.Printf("Received from %v: %s\n", remoteAddr, buf[:n])
// 响应
conn.WriteToUDP([]byte("Echo: "+string(buf[:n])), remoteAddr)
}
}
5.3.2.2. UDP 客户端
func main() {
addr, _ := net.ResolveUDPAddr("udp", "localhost:8080")
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
panic(err)
}
defer conn.Close()
conn.Write([]byte("Hello, UDP!"))
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
fmt.Printf("Response: %s\n", buf[:n])
}
5.3.2.3. ⚠️ UDP 陷阱
5.3.2.3.1. 陷阱 1:假设消息一定到达
// ❌ UDP 不保证消息送达
conn.Write(importantData)
// 数据可能丢失!
// ✅ 实现确认机制或使用 TCP
func sendWithRetry(conn *net.UDPConn, data []byte, maxRetries int) error {
for i := 0; i < maxRetries; i++ {
conn.Write(data)
conn.SetReadDeadline(time.Now().Add(time.Second))
ack := make([]byte, 4)
_, err := conn.Read(ack)
if err == nil && string(ack) == "ACK" {
return nil
}
}
return errors.New("send failed after retries")
}
5.3.2.3.2. 陷阱 2:假设消息顺序
// UDP 不保证顺序,需要自己处理
type Message struct {
SeqNum uint32
Payload []byte
}
// 接收端需要重排序
5.3.3. 连接池
type ConnPool struct {
mu sync.Mutex
conns chan net.Conn
addr string
}
func NewConnPool(addr string, size int) *ConnPool {
pool := &ConnPool{
conns: make(chan net.Conn, size),
addr: addr,
}
// 预创建连接
for i := 0; i < size; i++ {
conn, _ := net.Dial("tcp", addr)
pool.conns <- conn
}
return pool
}
func (p *ConnPool) Get() net.Conn {
return <-p.conns
}
func (p *ConnPool) Put(conn net.Conn) {
p.conns <- conn
}