《Go Cookbook CN》系列 16:TCP/UDP服务器与客户端开发全解
本篇聚焦Go语言网络编程的核心基础,系统讲解TCP/UDP两大传输层协议的服务端与客户端开发流程,结合net包的核心API拆解实战步骤,同时配套netcat工具的测试方法。学完本篇,你将掌握Go中TCP/UDP网络程序的完整开发逻辑,理解套接字编程的核心原理,并能独立实现可运行的TCP/UDP服务端与客户端。
【本篇核心收获】
- 理解OSI/TCP/IP协议分层模型、IP地址(IPv4/IPv6)、端口的核心概念,以及TCP/UDP协议的核心差异
- 掌握Go
net包创建TCP服务器/客户端的完整流程(监听、接受连接、数据读写),并能处理大尺寸数据 - 掌握Go
net包创建UDP服务器/客户端的完整流程(数据包监听、读写、地址交互) - 学会使用netcat(nc)工具测试TCP/UDP网络程序的连通性与功能有效性
- 了解Go网络编程的进阶优化点(IPv6支持、底层TCP/UDP连接精细控制、goroutine处理多连接)
16.0 网络编程核心基础认知
计算机网络的核心价值在于多设备协同计算与资源共享,而网络协议是设备通信的规则,通常被抽象为分层模型。这是实现网络编程的前置基础,也是理解套接字(Socket)编程的核心前提。
16.0.1 网络协议分层模型
网络协议分层是为了简化通信逻辑,主流有两种模型:
| 模型类型 | 分层结构 | 核心说明 |
|---|---|---|
| OSI模型 | 七层(应用层、表示层、会话层、传输层、网络层、数据链路层、物理层) | 理论化分层,全面但复杂度高 |
| TCP/IP模型(互联网协议套件) | 四层(应用层、传输层、互联网层、链路层) | 实际应用最广泛,简化了分层逻辑 |
16.0.2 核心分层协议与关键概念
1. 应用层
定义应用程序间的通信规则,如HTTP(80端口)、FTP(20/21端口)等,是直接面向业务的层级。
2. 传输层
负责端到端的数据包传输,核心协议有两个:
- TCP:面向连接、可靠传输,通过确认机制和序列号保证数据双向、可靠、有序传输,开销较高
- UDP:无连接、不可靠传输,不保证数据包的传递/顺序/重复性,但开销小、速度快
3. 互联网层
定义数据的组织形式(数据报)和设备寻址规则,核心是IP协议:
- IPv4:4字节地址,点分四元组(如
192.168.1.1),取值0~255,总地址约40亿个,已濒临耗尽 - IPv6:16字节地址,八位冒号分隔的十六进制数(如
2001:0db8:85a3:0000:0000:8a2e:0370:7334),地址空间超340万亿亿亿个
4. 端口
网络端点标识特定服务的数字,范围1~65535:
- 1~1023:保留给标准服务(如HTTP=80、FTP=21)
- 1024~65535:自定义服务使用
16.0.3 Go网络编程核心工具
Go标准库net包提供了完整的套接字编程能力,是实现TCP/UDP程序的核心依赖。同时,测试网络程序需用到netcat工具:
- 类Unix系统(macOS/Linux):默认安装,命令为
nc - Windows:需从Nmap项目等渠道下载
- 核心用途:通过TCP/UDP读写网络数据,测试端口可用性与程序连通性
模块小结
本模块梳理了网络编程的核心前置概念:协议分层模型、TCP/UDP特性、IP/端口规则,以及Go开发和测试的核心工具,是后续实现TCP/UDP程序的基础认知。
16.1 创建TCP服务器
16.1.1 核心原理
TCP是面向连接的可靠传输协议,套接字(Socket)是网络通信的端点,抽象了底层网络复杂度。TCP服务器的核心流程为:监听传入连接 → 接受连接 → 读写数据,且需通过goroutine处理多客户端并发连接。
16.1.2 实现步骤(基础版)
步骤1:创建监听器
使用net.Listen监听指定地址和端口,格式为host:port(空host表示监听所有网络接口)。
步骤2:循环接受连接
通过listener.Accept()阻塞等待并接收客户端连接,返回net.Conn连接对象。
步骤3:goroutine处理连接
为每个连接启动独立goroutine,避免阻塞主线程,实现多客户端并发处理。
步骤4:数据读写与连接关闭
从连接读取数据,按需写入响应,处理完成后关闭连接。
16.1.3 基础版代码实现
package main
import (
"io"
"log"
"net"
)
func main() {
// 1. 创建监听器,监听本地 9000 端口上的 TCP 连接
listener, err := net.Listen("tcp", "localhost:9000")
if err != nil {
log.Fatal(err)
}
defer listener.Close() // 确保程序退出时关闭监听器
for {
// 2. 接受传入的连接
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
// 3. 为每个连接启动一个 goroutine 进行处理
go func(c net.Conn) {
defer c.Close() // 处理完毕后关闭连接
buf := make([]byte, 1024)
n, err := c.Read(buf) // 从连接中读取数据
if err != nil {
log.Println("读取错误:", err)
return
}
log.Printf("收到: %s", string(buf[:n]))
// 向客户端发送响应
_, err = c.Write([]byte("Hello from TCP server"))
if err != nil {
log.Println("写入错误:", err)
}
}(conn)
}
}16.1.4 程序测试
- 启动服务器:
go run server.go- 打开新终端,用nc作为客户端发送数据:
$ echo "Hello from TCP client" | nc localhost 9000
Hello from TCP server测试结果:客户端发送的字符串会被服务器打印,同时服务器返回指定响应。
16.1.5 进阶优化:处理大尺寸数据
若客户端发送数据超过缓冲区大小,需循环读取直到遇到io.EOF(数据结束):
go func(c net.Conn) {
defer c.Close()
var data []byte
buf := make([]byte, 32) // 使用较小的缓冲区分批读取
for {
n, err := c.Read(buf)
if err != nil {
if err == io.EOF {
break // 数据已全部读取
}
log.Println("读取错误:", err)
return
}
data = append(data, buf[:n]...)
}
log.Printf("收到完整数据: %s", string(data))
_, err := c.Write([]byte("Hello from TCP server"))
if err != nil {
log.Println("写入错误:", err)
}
}(conn)16.1.6 进阶优化:IPv6支持
将监听地址的host部分留空,即可同时监听IPv4和IPv6:
listener, err := net.Listen("tcp", ":9000")客户端强制使用IPv6连接:
echo "Hello" | nc -6 localhost 900016.1.7 进阶优化:底层TCP连接精细控制
若需设置KeepAlive等TCP属性,需使用net.ListenTCP和AcceptTCP:
addr, _ := net.ResolveTCPAddr("tcp", ":9000")
listener, _ := net.ListenTCP("tcp", addr)
defer listener.Close()
for {
conn, _ := listener.AcceptTCP()
conn.SetKeepAlive(true) // 可以设置 TCP 保活等属性
// ... 处理连接
}模块小结
本模块完整讲解了TCP服务器的开发流程:从核心原理到基础实现,再到测试和进阶优化(大尺寸数据、IPv6、底层控制),掌握了goroutine处理多连接、数据读写的核心逻辑。
16.2 创建TCP客户端
16.2.1 核心原理
TCP客户端的核心是通过net.Dial建立与服务器的TCP连接,然后通过连接对象完成数据发送(可选接收响应),流程比服务器更简单。
16.2.2 实现步骤
- 连接服务器:使用
net.Dial("tcp", 地址)建立TCP连接。 - 发送数据:通过
conn.Write将字节切片数据发送到服务器。 - 关闭连接:使用
defer确保连接最终关闭。
16.2.3 基础版代码实现
package main
import (
"log"
"net"
)
func main() {
// 连接到本地的 TCP 服务器
conn, err := net.Dial("tcp", "localhost:9000")
if err != nil {
log.Fatal(err)
}
defer conn.Close() // 确保关闭连接
// 向服务器发送数据
message := "Hello World from TCP client"
n, err := conn.Write([]byte(message))
if err != nil {
log.Fatal(err)
}
log.Printf("成功发送 %d 字节", n)
}16.2.4 程序测试
- 启动nc监听服务器(模拟TCP服务端):
nc -l 9000- 运行客户端程序:
go run client.go测试结果:nc终端会显示客户端发送的消息,客户端日志打印发送的字节数。
16.2.5 进阶优化:底层TCP连接精细控制
使用net.DialTCP可指定本地地址等精细配置:
raddr, _ := net.ResolveTCPAddr("tcp", "localhost:9000")
conn, _ := net.DialTCP("tcp", nil, raddr) // 本地地址为 nil,系统自动选择
defer conn.Close()
conn.Write([]byte("Hello World from TCP Client"))模块小结
本模块讲解了TCP客户端的核心实现逻辑:从连接建立到数据发送,再到进阶的底层连接控制,掌握了net.Dial/DialTCP的使用,以及与模拟服务器的测试方法。
16.3 创建UDP服务器
16.3.1 核心原理
UDP是无连接协议,通信无需建立持久连接,每个数据包独立路由。UDP服务器的核心流程为:监听UDP数据包 → 读取数据包并获取客户端地址 → 向客户端地址回复数据,核心API为net.ListenPacket。
16.3.2 实现步骤
- 监听UDP端口:使用
net.ListenPacket("udp", 地址)创建数据包连接。 - 循环读取数据包:通过
ReadFrom读取数据并获取客户端地址。 - 回复客户端:通过
WriteTo向指定客户端地址发送响应。
16.3.3 基础版代码实现
package main
import (
"log"
"net"
)
func main() {
// 监听 UDP 9001 端口
conn, err := net.ListenPacket("udp", ":9001")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
buf := make([]byte, 1024)
for {
// 读取数据包,并获取客户端地址
n, addr, err := conn.ReadFrom(buf)
if err != nil {
log.Println("读取错误:", err)
continue
}
log.Printf("从 %s 收到: %s", addr.String(), string(buf[:n]))
// 向该客户端回复
_, err = conn.WriteTo([]byte("Hello from UDP server"), addr)
if err != nil {
log.Println("回复错误:", err)
}
}
}16.3.4 程序测试
- 启动UDP服务器:
go run udp_server.go- 打开新终端,用nc作为UDP客户端发送数据:
$ echo "Hello from UDP client" | nc -u localhost 9001
Hello from UDP server测试结果:服务器打印客户端地址和接收的数据,同时向客户端返回指定响应。
16.3.5 进阶优化:底层UDP连接精细控制
使用net.ListenUDP可获得*net.UDPConn,提供ReadFromUDP/WriteToUDP等专属方法:
addr, _ := net.ResolveUDPAddr("udp", ":9001")
conn, _ := net.ListenUDP("udp", addr) // 返回 *net.UDPConn
defer conn.Close()
buf := make([]byte, 1024)
for {
n, cliAddr, _ := conn.ReadFromUDP(buf)
log.Printf("从 %s 收到: %s", cliAddr, string(buf[:n]))
conn.WriteToUDP([]byte("Reply"), cliAddr)
}注:net.ListenPacket更通用(支持其他数据包协议),net.ListenUDP专为UDP设计,提供更多控制选项。
模块小结
本模块讲解了UDP服务器的核心实现:从无连接协议的原理,到基础版代码开发、测试,再到进阶的底层连接控制,掌握了数据包读写和地址交互的核心逻辑。
16.4 创建UDP客户端
16.4.1 核心原理
UDP客户端无需建立持久连接,核心是通过net.Dial("udp", 地址)创建连接对象,直接向服务器发送数据包,流程与TCP客户端类似,但协议层无连接保障。
16.4.2 实现步骤
- 连接UDP服务器:使用
net.Dial("udp", 地址)创建UDP连接。 - 发送数据:通过
conn.Write发送字节切片数据。 - 关闭连接:使用
defer确保连接关闭。
16.4.3 基础版代码实现
package main
import (
"log"
"net"
)
func main() {
// 连接到 UDP 服务器
conn, err := net.Dial("udp", "localhost:9001")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 发送数据
_, err = conn.Write([]byte("Hello from UDP client"))
if err != nil {
log.Fatal(err)
}
log.Println("数据已发送")
}16.4.4 程序测试
- 启动nc UDP监听服务器(模拟服务端):
nc -u -l 9001- 运行UDP客户端:
go run udp_client.go测试结果:nc终端显示客户端发送的消息,客户端日志打印“数据已发送”。
16.4.5 进阶优化:底层UDP连接精细控制
使用net.DialUDP可指定本地地址等精细配置:
raddr, _ := net.ResolveUDPAddr("udp", "localhost:9001")
conn, _ := net.DialUDP("udp", nil, raddr) // 本地地址为 nil
defer conn.Close()
conn.Write([]byte("Hello from UDP client"))模块小结
本模块讲解了UDP客户端的核心开发逻辑:从连接创建到数据发送,再到进阶的底层连接控制,掌握了UDP无连接特性下的客户端实现与测试方法。
【本篇核心知识点速记】
核心概念:
- TCP:面向连接、可靠、有序,开销高;UDP:无连接、不可靠、速度快
- IPv4(4字节)地址耗尽,IPv6(16字节)是未来主流;端口1~1023为保留端口
- 套接字(Socket)是网络通信端点,Go
net包是套接字编程的核心依赖 - netcat(nc)是测试网络程序的核心工具,支持TCP/UDP协议
TCP编程核心:
- 服务器:
net.Listen监听 →Accept接受连接 → goroutine处理连接 →Read/Write读写数据 - 客户端:
net.Dial建立连接 →Write发送数据 → 关闭连接 - 进阶:循环读取处理大尺寸数据、空host监听IPv4/IPv6、
ListenTCP/DialTCP精细控制连接属性
- 服务器:
UDP编程核心:
- 服务器:
net.ListenPacket监听 →ReadFrom读数据包+客户端地址 →WriteTo回复 - 客户端:
net.Dial创建连接 →Write发送数据包 → 关闭连接 - 进阶:
ListenUDP/DialUDP提供UDP专属的精细控制能力
- 服务器:
测试方法:
- TCP服务器测试:nc直接连接端口发送数据
- TCP客户端测试:nc -l 端口模拟服务器
- UDP服务器测试:nc -u 连接端口发送数据
- UDP客户端测试:nc -u -l 端口模拟服务器
