跳到主要内容

TCP/UDP 编程

问题

Go 如何实现 TCP/UDP 编程?goroutine-per-connection 模型是什么?

答案

TCP Server

func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer listener.Close()

for {
conn, err := listener.Accept()
if err != nil {
log.Println("accept error:", err)
continue
}
// 每个连接一个 goroutine
go handleConn(conn)
}
}

func handleConn(conn net.Conn) {
defer conn.Close()

// 设置读超时
conn.SetReadDeadline(time.Now().Add(30 * time.Second))

buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
log.Println("read error:", err)
}
return
}
// Echo 回去
conn.Write(buf[:n])
}
}

TCP Client

conn, err := net.DialTimeout("tcp", "localhost:8080", 5*time.Second)
if err != nil {
log.Fatal(err)
}
defer conn.Close()

conn.Write([]byte("hello"))

buf := make([]byte, 1024)
n, _ := conn.Read(buf)
fmt.Println(string(buf[:n]))

UDP

// UDP Server
addr, _ := net.ResolveUDPAddr("udp", ":8080")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()

buf := make([]byte, 1024)
for {
n, remoteAddr, _ := conn.ReadFromUDP(buf)
fmt.Printf("Received from %s: %s\n", remoteAddr, buf[:n])
conn.WriteToUDP([]byte("ACK"), remoteAddr)
}

TCP 粘包问题

TCP 是字节流协议,需要自行定义消息边界:

// 方案:长度前缀协议(4 字节长度 + 数据)
func sendMessage(conn net.Conn, data []byte) error {
length := uint32(len(data))
// 写入 4 字节长度头
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
return err
}
_, err := conn.Write(data)
return err
}

func readMessage(conn net.Conn) ([]byte, error) {
var length uint32
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
return nil, err
}
data := make([]byte, length)
_, err := io.ReadFull(conn, data)
return data, err
}

常见面试问题

Q1: goroutine-per-connection 为什么可行?

答案

在 Java/C++ 中,线程太重(~1MB 栈 + OS 调度开销),不能每个连接一个线程。Go 的 goroutine 只有 ~2KB 栈,百万并发连接只需几 GB 内存。底层 netpoll 自动使用 epoll/kqueue 做 IO 多路复用,goroutine 在阻塞时不占 CPU。

Q2: Go 的 net.Conn.Read 是阻塞的还是非阻塞的?

答案

代码角度看是阻塞的(直接调用 Read 等待数据)。从运行时角度看是非阻塞的——当 Read 没有数据时,goroutine 被挂起,底层用 epoll 等待。当数据到达时唤醒 goroutine。

这就是 Go 的设计理念:代码同步写,运行时异步执行

相关链接