使用随机端口启动应用是一个很常见的需求,常用于在微服务架构中做负载均衡。在 Go 中生成随机端口相对比较容易,可以通过使用 net 包中的方法就能很快实现。
实现原理
当 net.ListenTCP 的参数中指定的端口号为 0 时,操作系统将会随机分配一个可用的端口号。这种方式常被称为动态端口绑定。
具体来说,当你将端口号参数设置为 0 时,操作系统会在调用 net.ListenTCP 时自动选择一个未被占用的端口号,并将其绑定到监听器。然后通过 listener.Addr()
方法获取实际分配的端口号。
请注意,由于端口是动态分配的,因此在监听之前无法预先知道所分配的端口号。因此,需要通过 listener.Addr().(*net.TCPAddr).Port
或其他方式来获取实际的端口号。
主要使用 net 包中的的以下两个方法:
ResolveTCPAddr
func ResolveTCPAddr(network, address string) (*TCPAddr, error)
- network 参数指定网络类型,通常为 “tcp”。
- address 参数是要解析的地址字符串,可以是 IP 地址或主机名加端口号的形式,例如 “127.0.0.1:8080” 或 “localhost:8080”。
- 只负责解析地址字符串,而不会尝试建立连接。
ListenTCP
func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
- ListenTCP 在本地 TCP 地址 laddr 上声明并返回一个 *TCPListener,
- net 可选的值必须是 tcp、tcp4、tcp6 其中一个
- 如果 laddr 的端口字段为 0,函数将选择一个当前可用的端口
- 可以用 Listener 的 Addr 方法获得该端口。
示例代码
package main
import (
"fmt"
"net")
// GetFreePort 生成端口号
func GetFreePort() (port int, err error) {
// 解析地址
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
// 监听地址
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
// 获取动态分配的端口号
return l.Addr().(*net.TCPAddr).Port, nil
}
func main() {
port, _ := GetFreePort()
fmt.Println(port)
}
代码整理后如下
// GetFreePort asks the kernel for a free open port that is ready to use.
func GetFreePort() (port int, err error) {
var a *net.TCPAddr
if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil {
var l *net.TCPListener
if l, err = net.ListenTCP("tcp", a); err == nil {
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
}
return
}