Golang中net.DialTCP指定本地IP地址报错:address already in use
Golang中net.DialTCP指定本地IP地址报错:address already in use 编写了一个TCP客户端,用于创建10个TCP并发连接(长时间保持),并且只启用10个临时端口使用(49001 - 49010,通过 ip_local_port_range 文件设置)。
func createConnection(c int, desAddr, desPort string) (brokerCon net.Conn, err error) {
localips := GetLocalIP()
maxRetry := len(localips)
retry := 0
for retry < maxRetry {
lIPPort := fmt.Sprintf("%s:0", strings.Split(localips[retry].String(), "/")[0])
fmt.Println("\n---", lIPPort)
laddr, lerr := net.ResolveTCPAddr("tcp4", lIPPort)
if lerr != nil {
fmt.Println("Getting Error ResolveTCPAddr for local ", lerr)
}
raddr, rerr := net.ResolveTCPAddr("tcp4", desAddr+desPort)
if rerr != nil {
fmt.Println("Getting Error ResolveTCPAddr for local ", rerr)
}
brokerCon, err = net.DialTCP("tcp", laddr, raddr)
if err != nil {
fmt.Printf("Failed to connect connetion %d , %d retrying with seconday IP, err:\n",
c, retry, err)
retry = retry + 1
time.Sleep(1 * time.Second)
continue
} else {
//fmt.Println("successfull ")
break
}
}
return
}
场景 1:
如果 DialTCP 方法中的 laddr 值为 nil,它能够成功创建10个连接。DialTCP 能够使用已经被其他不同目标连接占用的端口 49009 和 49007(因为一个TCP连接由五元组决定:[本地IP, 本地端口, 远程IP, 远程端口, 协议])。
sudo netstat -anpl --tcp --udp | grep 490
tcp 0 0 10.50.1.245:49005 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49010 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49008 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49009 XXX.XXX.XXX.X44:443 ESTABLISHED 3250/XXXX
tcp 0 0 10.50.1.245:49006 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49002 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49004 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49001 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49007 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49003 10.50.1.41:9999 ESTABLISHED 23408/client
tcp 0 0 10.50.1.245:49007 XXX.XXX.XXX.X29:443 ESTABLISHED 2806/XXXX
tcp 0 0 10.50.1.245:49009 10.50.1.41:9999 ESTABLISHED 23408/client
场景 2:
如果我传入本地IP详情(10.50.1.245)并指定端口为0,它只能创建8个连接,对于剩余的2个连接会抛出 bind: address already in use 错误。如果本地地址不为 nil,为什么 dialTCP 无法使用已经被其他不同目标连接占用的端口 49005 和 49007?
sudo netstat -anpl --tcp --udp | grep 490
tcp 0 0 10.50.1.245:49010 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49005 XXX.XXX.XXX.X66:443 ESTABLISHED 2510/XXX
tcp 0 0 10.50.1.245:49008 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49005 XXX.XXX.XXX.X44:443 ESTABLISHED 3250/XXX
tcp 0 0 10.50.1.245:49006 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49002 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49004 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49001 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49003 10.50.1.41:9999 ESTABLISHED 25841/client
tcp 0 0 10.50.1.245:49007 XXX.XXX.XXX.X29:443 ESTABLISHED 2806/XXX
tcp 0 0 10.50.1.245:49009 10.50.1.41:9999 ESTABLISHED 25841/client
更多关于Golang中net.DialTCP指定本地IP地址报错:address already in use的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中net.DialTCP指定本地IP地址报错:address already in use的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这个问题的根本原因是当指定本地IP地址时,Go的TCP连接行为发生了变化。当laddr为nil时,系统会自动选择可用的本地端口,即使该端口已被其他连接使用(只要五元组不同)。但当指定了具体的本地IP地址时,系统会尝试绑定到该IP地址的特定端口(即使端口为0),这时如果端口已被占用就会失败。
以下是问题分析和解决方案:
问题分析
当指定本地IP地址时,net.DialTCP会尝试绑定到该IP地址。即使端口设为0,系统也会在绑定过程中检查该IP地址上是否有冲突。问题在于Linux的端口重用策略。
解决方案
使用SO_REUSEADDR和SO_REUSEPORT套接字选项来允许端口重用:
package main
import (
"context"
"fmt"
"net"
"syscall"
"time"
)
type Dialer struct {
control func(network, address string, c syscall.RawConn) error
}
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
var dialer net.Dialer
dialer.Control = d.control
return dialer.DialContext(ctx, network, address)
}
func createConnectionWithReuse(c int, desAddr, desPort, localIP string) (net.Conn, error) {
dialer := &Dialer{
control: func(network, address string, c syscall.RawConn) error {
var opErr error
err := c.Control(func(fd uintptr) {
// 设置SO_REUSEADDR和SO_REUSEPORT
opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if opErr != nil {
return
}
opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
})
if err != nil {
return err
}
return opErr
},
}
ctx := context.Background()
localAddr := net.JoinHostPort(localIP, "0")
remoteAddr := net.JoinHostPort(desAddr, desPort)
return dialer.DialContext(ctx, "tcp4", remoteAddr)
}
// 或者使用更简洁的方式
func createConnectionSimple(c int, desAddr, desPort, localIP string) (net.Conn, error) {
dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{
IP: net.ParseIP(localIP),
Port: 0,
},
Control: func(network, address string, c syscall.RawConn) error {
var opErr error
err := c.Control(func(fd uintptr) {
opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if opErr != nil {
return
}
opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
})
if err != nil {
return err
}
return opErr
},
}
return dialer.Dial("tcp4", net.JoinHostPort(desAddr, desPort))
}
// 批量创建连接的示例
func createMultipleConnections(desAddr, desPort, localIP string, count int) ([]net.Conn, error) {
connections := make([]net.Conn, 0, count)
for i := 0; i < count; i++ {
conn, err := createConnectionSimple(i, desAddr, desPort, localIP)
if err != nil {
// 关闭已创建的连接
for _, c := range connections {
c.Close()
}
return nil, fmt.Errorf("failed to create connection %d: %v", i, err)
}
connections = append(connections, conn)
fmt.Printf("Created connection %d: local=%s remote=%s\n",
i, conn.LocalAddr(), conn.RemoteAddr())
}
return connections, nil
}
func main() {
// 使用示例
conns, err := createMultipleConnections("10.50.1.41", "9999", "10.50.1.245", 10)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// 保持连接
for _, conn := range conns {
defer conn.Close()
}
// 保持程序运行
select {}
}
关键点说明
SO_REUSEADDR:允许绑定到处于TIME_WAIT状态的地址SO_REUSEPORT:允许多个套接字绑定到相同的IP地址和端口组合
这两个选项的组合允许在指定本地IP地址时重用端口,即使该端口已被其他连接使用(只要五元组不同)。
替代方案
如果不想使用套接字选项,可以考虑让系统自动选择本地地址:
func createConnectionAuto(c int, desAddr, desPort string) (net.Conn, error) {
// 不指定LocalAddr,让系统自动选择
dialer := &net.Dialer{
Timeout: 30 * time.Second,
}
return dialer.Dial("tcp4", net.JoinHostPort(desAddr, desPort))
}
这种方法最简单,但无法控制使用哪个本地IP地址(在多IP系统中)。

