文章目录

Go 网络编程:net 包与 TCP/UDP

发布于 2026-04-07 05:46:31 · 浏览 10 次 · 评论 0 条

Go 网络编程:net 包与 TCP/UDP

网络编程的核心在于建立数据传输通道并稳定交换信息。Go 语言标准库 net 封装了底层操作系统 Socket 接口,提供了一套高度统一的 API。以下指南将直接演示如何编写可运行的 TCP 与 UDP 服务,并明确生产环境中的资源管理与协议选型规范。


阶段一:初始化项目环境

  1. 打开 系统终端或命令行工具。
  2. 创建 独立工作目录,执行 mkdir go-net-tutorial
  3. 切换 至新目录,执行 cd go-net-tutorial
  4. 初始化 模块依赖,执行 go mod init go-net-tutorial。该命令会在当前路径生成 go.mod 清单文件,用于声明包路径与依赖版本。

阶段二:构建 TCP 可靠通信链路

TCP 协议提供面向连接、按序到达、自动重传的字节流服务。通过三次握手建立虚拟通道,适用于文件同步、数据库交互等对数据完整性要求严格的业务。

  1. 创建 服务端源码,执行 touch tcp_server.go
  2. 写入 完整监听逻辑。代码通过 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]))
}
  1. 验证 全双工通信。打开新终端窗口执行 go run tcp_client.go,对照两端日志确认请求与响应报文完全匹配。

阶段三:构建 UDP 低延迟通信链路

UDP 协议采用无连接的数据报模式,不维护状态机,不保证送达顺序。头部仅占用 8 字节,适用于实时音视频、传感器遥测或高频心跳检测。

  1. 创建 服务端文件 touch udp_server.go
  2. 编写 数据报接收代码。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]))
}
  1. 执行测试。在独立终端运行 go run udp_client.go,观察服务端即时打印的接收日志与客户端打印的响应内容。

阶段四:协议对比与生产调优策略

工程实践中需严格依据业务容忍度选择底层协议。TCP 牺牲部分延迟换取绝对可靠,UDP 放弃重传机制换取极致吞吐。正确配置资源边界与超时策略,是保障服务长期稳定运行的关键。

  1. 查阅 核心指标差异,依据下表快速匹配技术栈。
评估维度 TCP 协议栈 UDP 协议栈
连接建立 需完成三次握手建立状态 直接发送数据报无需握手
传输边界 字节流模式,无自然报文边界 数据报模式,天然保留包边界
拥塞控制 内置滑动窗口与慢启动算法 由应用层自行实现或忽略
头部体积 20 字节基础头部 + 选项字段 固定 8 字节
  1. 配置 读写超时阈值。长时间挂起的 conn.Read() 会持续占用文件描述符。在业务循环起始处 调用 conn.SetReadDeadline(time.Now().Add(30 * time.Second)) 强制中断空闲连接。捕获超时错误后 主动关闭 套接字。
  2. 复用 读取缓冲区。高频调用 make([]byte, 4096) 将触发频繁的小对象分配与垃圾回收停顿。声明 全局 sync.Pool 缓存 切片引用,读取前执行 Get 获取内存,读取清理后执行 Put 归还。此操作可显著降低 GC STW 延迟。
  3. 限制 协程并发数量。无限制地 go handle() 会导致文件描述符耗尽。在入口处 创建 带缓冲通道 sem := make(chan struct{}, 10000)。接受连接前 尝试发送 struct{}{} 到通道,处理完毕后从通道 接收 释放槽位。通道满载时拒绝新连接,保护系统不被压垮。
  4. 处理 TCP 粘包与拆包现象。TCP 是流协议,应用层发送的两次 Write 可能被底层合并为一次网络包到达。定义 报文结构为 4字节长度头 + 实际业务载荷。在接收端 分配 环形缓冲区,读取 前 4 字节解析长度,累积 字节数达标后再 剥离 完整消息交由业务逻辑解析。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文