Go 网络协议实战(二): Socket 与 net 包——编程模型

张开发
2026/5/23 9:15:31 15 分钟阅读
Go 网络协议实战(二): Socket 与 net 包——编程模型
Go 网络协议实战(二): Socket 与 net 包——编程模型上一篇我们用 Go 写了 TCP 和 UDP 的「发名字、收 Hello」用到了net.Listen、net.Dial、net.Conn、net.PacketConn。这些 API 在操作系统里对应的东西叫Socket。Socket 不是一种协议而是用 TCP/UDP 的编程接口——就像你要开车得先有个方向盘Socket 就是应用层拿来“发数据、收数据”的那个把手。这篇把 Socket 是什么、和 TCP/UDP 的关系、Go 的net包怎么包一层说清楚后面看 HTTP、gRPC 时就不会懵。Socket 是什么在协议栈的哪一层Socket最早出自 Berkeley Unix后来成了 POSIX 和 Windows 上做网络编程的标准 API。它不关心“包怎么组、丢了怎么重传”这些细节只管你创建一个 socket把它和“地址 用什么协议TCP 还是 UDP”绑在一起然后 Read/Write剩下的交给内核和协议栈。所以协议栈里并没有单独一层叫“Socket”。Socket 是横在应用层和传输层之间的编程抽象你要用 TCP 就建一个“面向连接”的 socket要用 UDP 就建一个“按包收发”的 socket。协议还是 TCP/UDPSocket 只是“怎么用它们”的接口。两种 Socket 类型stream 与 datagram在 BSD/POSIX 里和咱们最相关的就两种类型常量对应协议语义streamSOCK_STREAMTCP先建连再读写字节流、可靠datagramSOCK_DGRAMUDP不建连按包收发不保证可靠可以记成TCP 像打电话先接通再说话UDP 像发短信一条条发不用先“接通”。datagram的典型用法比如实时游戏、直播推流——更看重低延迟、可以接受偶尔丢包就用按包收发的 UDP。stream这边举个身边的例子调用大模型时的流式响应——你发一个请求模型在一条已建好的连接上持续把 token 推给你你这边就是在这条连接上不断Read这就是典型的 stream 语义一条连接、字节流陆续到达。上一篇里服务端用bufio.Scanner从conn按行读本质也是在这条 stream 上读字节流。上一篇写的 TCP 和 UDP 代码本质上就是在用这两种 socket。Go 的 net 包Listener、Conn、PacketConnGo 标准库net把上面说的 socket 包了一层跨平台统一你不用自己去调 C 的socket()/bind()/listen()。在操作系统里上一篇那些调用大概是这样对应的net.Listen(tcp, addr)相当于socket()bind()listen()Accept()对应accept()net.Dial(tcp, addr)相当于socket()connect()。UDP 类似只不过类型是SOCK_DGRAM不建连时用sendto()/recvfrom()带上对方地址。TCP 服务端net.Listen(tcp, addr)得到net.Listener底层是一条在监听的 stream socketAccept()得到net.Conn就是一条已经建好的 TCP 连接。TCP 客户端net.Dial(tcp, addr)直接得到net.Conn也是一条 stream socket。UDP 服务端net.ListenPacket(udp, addr)得到net.PacketConn收发包都要带对方地址因为没“连接”。UDP 客户端net.Dial(udp, addr)拿到的类型也实现了net.Conn所以可以Read/Write不用每次写地址内核只会收你连的那个地址发来的包。底层还是 datagram只是对端被固定了。总结一下net.Conn要么是一条 TCP 连接要么是“绑定了对端的 UDP”net.PacketConn是可以对多个地址收发的 UDP socket。上一篇里 TCP 用Listener.Accept()拿ConnUDP 用ListenPacket再ReadFrom/WriteTo就是这套模型。动手验证最小 TCP 示例与底层对应下面是一段可直接运行的最小 TCP 服务端每行关键调用旁边标出在操作系统里大致对应的含义。保存为server.gogo run server.go后可用echo hi | nc localhost 8080或第一篇的 TCP 客户端连上验证。packagemainimport(bufiofmtlognetstrings)funcmain(){addr:127.0.0.1:8080// 底层: socket(AF_INET, SOCK_STREAM, 0) bind() listen()lis,err:net.Listen(tcp,addr)iferr!nil{log.Fatal(err)}deferlis.Close()fmt.Println(listening on,addr)for{// 底层: accept()阻塞直到有新连接得到一条 stream socket 连接conn,err:lis.Accept()iferr!nil{log.Println(err)continue}// 在这条连接上 Read/Write即对一条 TCP 字节流读写gofunc(c net.Conn){deferc.Close()scanner:bufio.NewScanner(c)ifscanner.Scan(){name:strings.TrimSpace(scanner.Text())ifname{nameWorld}fmt.Fprintf(c,Hello, %s (from net.Listen)\n,name)}}(conn)}}对应关系小结net.Listen(tcp, addr)→ 创建并绑定、监听的stream socketAccept()→ 得到一条已建好的TCP 连接net.Conn在conn上Read/Write就是在这条连接对应的内核 socket 上收发字节流。UDP 版把Listen换成ListenPacket、用ReadFrom/WriteTo带地址对应的就是datagram socket的recvfrom/sendto。和第一篇的对应关系第一篇里的写法对应概念在 OS 里大概是什么net.Listen(tcp, addr)Listener在监听的 stream socketsocket bind listenlis.Accept()拿到一条 ConnTCP 连接acceptnet.Dial(tcp, addr)一条 Connsocket connectnet.ListenPacket(udp, addr)PacketConnsocket bindconn.ReadFrom/WriteTo无连接每次带对方地址recvfrom / sendtonet.Dial(udp, addr)能 Read/Write 的“伪连接”socket 内核帮你记着对端下面用第一篇的代码片段加上注释把「Go 调用 → 底层含义」标出来方便对着表看。仅为对应关系示意实际代码应对err做检查。// TCP 服务端第一篇lis,_:net.Listen(tcp,addr)// 底层: socket() bind() listen()conn,_:lis.Accept()// 底层: accept()得到一条 stream 连接gohandleTCPConn(conn)// 在这条连接上 Read/Write即字节流// TCP 客户端第一篇conn,_:net.Dial(tcp,addr)// 底层: socket() connect()fmt.Fprintln(conn,name)// 通过这条 stream 发数据大模型流式响应就是在类似连接上持续 Read// UDP 服务端第一篇conn,_:net.ListenPacket(udp,addr)// 底层: socket() bind()得到 PacketConnn,clientAddr,_:conn.ReadFrom(buf)// 底层: recvfrom()每次带上对方地址conn.WriteTo(resp,clientAddr)// 底层: sendto()回包要指定对端第一篇里handleTCPConn(conn)拿到的conn就是一条stream socket 连接UDP 里ReadFrom(buf)拿到的clientAddr就是这一包的对方地址回包时WriteTo(resp, clientAddr)。客户端用net.Dial(udp, addr)时本质仍是 datagram只是内核记着对端所以才能像Conn一样 Read/Write称为“伪连接”。下一篇会在这一层上面再加一层HTTP的格式约定。小结Socket编程接口不是协议应用层通过它去用传输层。TCP / UDP传输层协议Socket 用 stream / datagram 两种类型来用它们。Go 的 net 包对 Socket 的封装ListenerConn对应 TCPPacketConn对应 UDP。下一篇回到应用层看HTTP/1.1怎么在 TCP 上用“文本头 JSON”做请求和响应。本系列目录(一) 传输层 TCP 与 UDP |(二) Socket 与 net 包本篇| (三) HTTP/1.1 与最小服务端 | (四) HTTP/2 | (五) gRPC 与 Protobuf | (六) 综合对比与选型 | (七) 经验分享RTA 流量与成本优化

更多文章