Go 网络编程:net 包与 TCP/UDP
网络编程的核心在于建立数据传输通道并稳定交换信息。Go 语言标准库 net 封装了底层操作系统 Socket 接口,提供了一套高度统一的 API。以下指南将直接演示如何编写可运行的 TCP 与 UDP 服务,并明确生产环境中的资源管理与协议选型规范。
阶段一:初始化项目环境
- 打开 系统终端或命令行工具。
- 创建 独立工作目录,执行
mkdir go-net-tutorial。 - 切换 至新目录,执行
cd go-net-tutorial。 - 初始化 模块依赖,执行
go mod init go-net-tutorial。该命令会在当前路径生成go.mod清单文件,用于声明包路径与依赖版本。
阶段二:构建 TCP 可靠通信链路
TCP 协议提供面向连接、按序到达、自动重传的字节流服务。通过三次握手建立虚拟通道,适用于文件同步、数据库交互等对数据完整性要求严格的业务。
- 创建 服务端源码,执行
touch tcp_server.go。 - 写入 完整监听逻辑。代码通过
net.Listen绑定本地端口,并利用无限for循环阻塞等待新连接。每个Accept返回的连接对象均交由独立协程处理,确保主循环不被阻塞。package main
import (
"fmt"
"net"
"log"
)
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:9000")
if err != nil {
log.Fatal("监听端口失败:", err)
}
defer listener.Close()
fmt.Println("TCP 服务端就绪,监听地址: 127.0.0.1:9000")
for {
conn, err := listener.Accept()
if err != nil {
log.Println("接受连接异常:", err)
continue
}
go handleTCPClient(conn)
}
}
func handleTCPClient(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 2048)
for {
n, err := conn.Read(buf)
if err != nil {
// 客户端断开或读取超时时跳出循环
return
}
payload := string(buf[:n])
fmt.Printf("收到数据: %s\n", payload)
_, err = conn.Write([]byte("ACK: " + payload))
if err != nil {
return
}
}
}
3. **启动** 服务端,在终端运行 `go run tcp_server.go`。确认控制台打印就绪提示且进程保持驻留。
4. **创建** 客户端文件,执行 `touch tcp_client.go`。
5. **实现** 主动拨号逻辑。使用 `net.Dial` 建立流式通道,写入请求数据后阻塞读取响应。
```go
package main
import (
"fmt"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:9000")
if err != nil {
fmt.Println("拨号失败:", err)
os.Exit(1)
}
defer conn.Close()
msg := []byte("Ping TCP Server")
_, err = conn.Write(msg)
if err != nil {
fmt.Println("写入数据失败")
return
}
resp := make([]byte, 2048)
n, err := conn.Read(resp)
if err != nil {
fmt.Println("读取响应失败")
return
}
fmt.Printf("服务端返回: %s\n", string(resp[:n]))
}
- 验证 全双工通信。打开新终端窗口执行
go run tcp_client.go,对照两端日志确认请求与响应报文完全匹配。
阶段三:构建 UDP 低延迟通信链路
UDP 协议采用无连接的数据报模式,不维护状态机,不保证送达顺序。头部仅占用 8 字节,适用于实时音视频、传感器遥测或高频心跳检测。
- 创建 服务端文件
touch udp_server.go。 - 编写 数据报接收代码。UDP 通信必须显式解析远程网络地址,服务端通过
ReadFromUDP同时获取载荷字节与发送方标识,据此精准回传数据。package main
import (
"fmt"
"net"
"log"
)
func main() {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:9001")
if err != nil {
log.Fatal("地址解析错误:", err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal("UDP 监听失败:", err)
}
defer conn.Close()
fmt.Println("UDP 服务端就绪,监听: 127.0.0.1:9001")
buf := make([]byte, 1500) // UDP 常见 MTU 上限
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
continue
}
payload := string(buf[:n])
fmt.Printf("来自 %s: %s\n", remoteAddr, payload)
_, _ = conn.WriteToUDP([]byte("ACK UDP: "+payload), remoteAddr)
}
}
3. **运行** 服务端进程 `go run udp_server.go`。
4. **创建** 客户端文件 `touch udp_client.go`。
5. **实现** 客户端发包逻辑。调用 `DialUDP` 绑定目标地址后,直接调用 `Write` 将数据报推入网络栈。
```go
package main
import (
"fmt"
"net"
"os"
)
func main() {
srvAddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:9001")
conn, err := net.DialUDP("udp", nil, srvAddr)
if err != nil {
fmt.Println("创建 UDP 通道失败")
os.Exit(1)
}
defer conn.Close()
_, err = conn.Write([]byte("Sensor Data: Temp=25.6"))
if err != nil {
fmt.Println("发包异常")
return
}
buf := make([]byte, 1500)
n, _, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("收包异常")
return
}
fmt.Printf("响应: %s\n", string(buf[:n]))
}
- 执行测试。在独立终端运行
go run udp_client.go,观察服务端即时打印的接收日志与客户端打印的响应内容。
阶段四:协议对比与生产调优策略
工程实践中需严格依据业务容忍度选择底层协议。TCP 牺牲部分延迟换取绝对可靠,UDP 放弃重传机制换取极致吞吐。正确配置资源边界与超时策略,是保障服务长期稳定运行的关键。
- 查阅 核心指标差异,依据下表快速匹配技术栈。
| 评估维度 | TCP 协议栈 | UDP 协议栈 |
|---|---|---|
| 连接建立 | 需完成三次握手建立状态 | 直接发送数据报无需握手 |
| 传输边界 | 字节流模式,无自然报文边界 | 数据报模式,天然保留包边界 |
| 拥塞控制 | 内置滑动窗口与慢启动算法 | 由应用层自行实现或忽略 |
| 头部体积 | 20 字节基础头部 + 选项字段 | 固定 8 字节 |
- 配置 读写超时阈值。长时间挂起的
conn.Read()会持续占用文件描述符。在业务循环起始处 调用conn.SetReadDeadline(time.Now().Add(30 * time.Second))强制中断空闲连接。捕获超时错误后 主动关闭 套接字。 - 复用 读取缓冲区。高频调用
make([]byte, 4096)将触发频繁的小对象分配与垃圾回收停顿。声明 全局sync.Pool缓存 切片引用,读取前执行Get获取内存,读取清理后执行Put归还。此操作可显著降低GC STW延迟。 - 限制 协程并发数量。无限制地
go handle()会导致文件描述符耗尽。在入口处 创建 带缓冲通道sem := make(chan struct{}, 10000)。接受连接前 尝试发送struct{}{}到通道,处理完毕后从通道 接收 释放槽位。通道满载时拒绝新连接,保护系统不被压垮。 - 处理 TCP 粘包与拆包现象。TCP 是流协议,应用层发送的两次
Write可能被底层合并为一次网络包到达。定义 报文结构为4字节长度头 + 实际业务载荷。在接收端 分配 环形缓冲区,读取 前 4 字节解析长度,累积 字节数达标后再 剥离 完整消息交由业务逻辑解析。

暂无评论,快来抢沙发吧!