Skip to content

net ​

阅读提示

  • 适用版本:Go 1.21+(示例在 Go 1.22 下验证)· 前置知识:进阶-并发 · 阅读时长:约 15 分钟

Go 语言的net标准库是一个非常强大的库,它提供了处理网络通信、IP 地址、DNS 解析、TCP/UDP 协议、HTTP 协议等常见任务的功能。由于 Go 语言本身的并发特性,得益于此,Go 在处理网络 IO 的时候非常的简洁高效。

地址解析 ​

Go 提供了四个函数来解析网络地址,下面逐一讲解。

MAC 地址 ​

签名

go
func ParseMAC(s string) (hw HardwareAddr, err error)

示例

go
package main

import (
  "fmt"
  "net"
)

func main() {
  hw, err := net.ParseMAC("00:1A:2B:3C:4D:5E")
  if err != nil {
    panic(err)
  }
  fmt.Println(hw)
}

CIDR ​

签名

go
func ParseCIDR(s string) (IP, *IPNet, error)

示例

go
package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    ipv4Addr, ipv4Net, err := net.ParseCIDR("192.0.2.1/24")
    if err != nil {
       log.Fatal(err)
    }
    fmt.Println(ipv4Addr)
    fmt.Println(ipv4Net)
}

IP 地址 ​

IP 地址支持解析 ipv4,ipv6,函数签名如下

go
func ResolveIPAddr(network, address string) (*IPAddr, error)

使用示例如下

go
package main

import (
  "fmt"
  "net"
)

func main() {
  ipv4Addr, err := net.ResolveIPAddr("ip4", "192.168.2.1")
  if err != nil {
    panic(err)
  }
  fmt.Println(ipv4Addr)

  ipv6Addr, err := net.ResolveIPAddr("ip6", "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
  if err != nil {
    panic(err)
  }
  fmt.Println(ipv6Addr)
}

TCP 地址 ​

TCP 地址支持 tcp4,tcp6,签名如下

go
func ResolveTCPAddr(network, address string) (*TCPAddr, error)

使用示例如下

go
package main

import (
  "fmt"
  "net"
)

func main() {
  tcp4Addr, err := net.ResolveTCPAddr("tcp4", "0.0.0.0:2020")
  if err != nil {
    panic(err)
  }
  fmt.Println(tcp4Addr)
  tcp6Addr, err := net.ResolveTCPAddr("tcp6", "[::1]:8080")
  if err != nil {
    panic(err)
  }
  fmt.Println(tcp6Addr)
}

UDP 地址 ​

UDP 地址支持 udp4,udp6,签名如下

go
func ResolveUDPAddr(network, address string) (*UDPAddr, error)

使用示例如下

go
package main

import (
  "fmt"
  "net"
)

func main() {
  udp4Addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:2020")
  if err != nil {
    panic(err)
  }
  fmt.Println(udp4Addr)
  udp6Addr, err := net.ResolveUDPAddr("udp6", "[::1]:8080")
  if err != nil {
    panic(err)
  }
  fmt.Println(udp6Addr)
}

Unix 地址 ​

Unix 地址支持 unix,unixgram,unixpacket,签名如下

go
func ResolveUnixAddr(network, address string) (*UnixAddr, error)

使用示例如下

go
package main

import (
    "fmt"
    "net"
)

func main() {
    unixAddr, err := net.ResolveUnixAddr("unix", "/tmp/mysocket")
    if err != nil {
       panic(err)
    }
    fmt.Println(unixAddr)
}

DNS ​

Go 还提供了很多函数用于 DNS 查询,下面一个例子是解析域名的 IP 地址

go
package main

import (
  "fmt"
  "net"
)

func main() {
  addrs, err := net.LookupHost("github.com")
  if err != nil {
    panic(err)
  }
  fmt.Println(addrs)
}

查询记录值

go
package main

import (
  "fmt"
  "net"
)

func main() {
  mxs, err := net.LookupMX("github.com")
  if err != nil {
    panic(err)
  }
  fmt.Println(mxs)
}

网络编程 ​

tcp 编程的逻辑十分简单,对于客户端而言就是

  1. 建立连接
  2. 发送数据或读取数据
  3. 退出

对于服务端而言就是

  1. 监听地址
  2. 获取连接
  3. 新建一个协程去处理该连接

下面是一个简单的例子,客户端代码

go
package main

import (
  "net"
)

func main() {
  // 建立连接
  conn, err := net.Dial("tcp", "0.0.0.0:1234")
  if err != nil {
    panic(err)
  }
  defer conn.Close()

  // 发送数据
  for i := range 10 {
    _, err := conn.Write([]byte{'a' + byte(i)})
    if err != nil {
      panic(err)
    }
  }
}

服务端代码

go
package main

import (
  "errors"
  "fmt"
  "io"
  "net"
  "sync"
)

func main() {
  // 监听地址
  listener, err := net.Listen("tcp", "0.0.0.0:1234")
  if err != nil {
    panic(err)
  }
  defer listener.Close()

  var wg sync.WaitGroup

  for {
    // 阻塞等待下一个连接建立
    conn, err := listener.Accept()
    if err != nil {
      panic(err)
    }

    // 开启一个新的协程去异步处理该连接
    wg.Add(1)
    go func() {
      defer wg.Done()
      buf := make([]byte, 4096)
      for {
        // 从连接中读取数据
        n, err := conn.Read(buf)
        if errors.Is(err, io.EOF) {
          break
        } else if err != nil {
          panic(err)
        }

        data := string(buf[:n])
        fmt.Println(data)
      }
    }()
  }

  wg.Wait()
}

客户端发送数据,服务端接收数据,这个例子非常的简单,服务端建立新连接时,只需开启一个新的协程就可以去处理,不需要阻塞,UDP 大体上的写法也都是类似的。

完整示例 ​

下面的程序可以直接运行:复制到文件后执行 go run main.go,或粘贴到 Go Playground 运行。

go
package main

import (
	"fmt"
	"net"
)

func main() {
	ip := net.ParseIP("192.168.0.1")
	fmt.Println(ip != nil, ip.IsPrivate())

	host, port, err := net.SplitHostPort("example.com:8080")
	fmt.Println(host, port, err == nil)

	fmt.Println(net.JoinHostPort("127.0.0.1", "80"))
}

预期输出:

text
true true
example.com 8080 true
127.0.0.1:80

常见错误与踩坑 ​

  • net.ParseIP 失败返回 nil。 调用 IsPrivate 等方法前务必判空。
  • SplitHostPort 必须带端口。 没有冒号会返回错误;IPv6 要注意中括号写法。
  • Dial 默认没有超时。 生产代码应使用 DialTimeout 或带 context 的 dialer。
  • 连接一定要关闭。 客户端与服务端两侧都写 defer conn.Close()。
  • UDP 用 PacketConn。 它没有流式语义,需要自行处理数据报边界。

更新记录 ​

  • 2026-10-14:新增"阅读提示"、完整可运行示例(含预期输出)、常见错误与踩坑清单,以及本更新记录。