Golang错误:绑定地址已在使用中,即使端口上没有运行任何内容。

huangapple go评论69阅读模式
英文:

golang errors with bind address already in use even though nothing is running on the port

问题

我有一个在golang中的设置,基本上是从操作系统获取一个空闲端口,然后在该端口上启动一个HTTP服务器。它开始出现随机错误,报告端口注册失败。我将其简化为以下程序,似乎在获取几个空闲端口后出现错误。这种情况非常随机,并且在错误端口上没有真正运行的进程。对我来说,这完全没有意义,不明白为什么会出错。如果有任何帮助,将不胜感激。

程序的输出:

..<br/>
..<br/>
58479<br/>
..<br/>
..<br/>
58867<br/>
58868<br/>
58869<br/>
..<br/>
成功绑定! 58867<br/>
成功绑定! 58868<br/>
成功绑定! 58869<br/>
..<br/>
..<br/>
..<br/>
2015/04/28 09:05:09 绑定端口时出错:listen tcp :58479: bind: address already in use<br/>

我确保检查所得到的空闲端口不会重复。

package main

import (
    "net"
    "net/http"
    "log"
    "fmt"
)

func main() {

    for {
        l, _ := net.Listen("tcp", ":0")
        var port = l.Addr().String()[5:]
        l.Close()
        fmt.Println(port)
        go func() {
            l1, err := net.Listen("tcp", ":"+port)
            if err != nil {
                log.Fatal("绑定端口时出错:", err.Error())
            } else {
                fmt.Println("成功绑定! ", port)
            }
            http.Serve(l1, nil)
        }()
    }
}

希望对你有所帮助!

英文:

I have a setup in golang which basically gets a free port from OS and then starts a http sever on it. It started to give random errors with port signup failures. I simplified it into the following program which seems to error after grabbing a few free ports. It happens very randomly and there is no real process running on the port it errors. Doesn't make sense at all to me on why this has to error. Any help would be appreciated.

Output of the program:

..<br/>
..<br/>
58479<br/>
..<br/>
..<br/>
58867<br/>
58868<br/>
58869<br/>
..<br/>
bound well! 58867<br/>
bound well! 58868<br/>
bound well! 58869<br/>
..<br/>
..<br/>
..<br/>
2015/04/28 09:05:09 Error while binding port: listen tcp :58479: bind: address already in use<br/>

I made sure to check that the free port that came out never repeated.

package main

 import (
    &quot;net&quot;
    &quot;net/http&quot;
    &quot;log&quot;
    &quot;fmt&quot;
)

func main() {

    for {
        l, _ := net.Listen(&quot;tcp&quot;, &quot;:0&quot;)
        var port = l.Addr().String()[5:]
        l.Close()
        fmt.Println(port)
        go func() {
                l1, err := net.Listen(&quot;tcp&quot;, &quot;:&quot;+port)
                if (err != nil) {
                    log.Fatal(&quot;Error while binding port: &quot;, err.Error())
                } else {
                    fmt.Println(&quot;bound well! &quot;, port)
                }
                http.Serve(l1, nil)
            }()
    }
}

答案1

得分: 11

你所做的是在某一点检查端口是否空闲,然后基于它过去是空闲的事实来尝试使用它。这样是行不通的。

发生的情况是:在每次 for 循环迭代中,你生成一个端口号并确保它是空闲的。然后,你生成一个用于使用此端口的例程(该端口已释放回空闲端口池)。你并不真正知道例程何时启动。它可能在主例程(for 循环)刚刚生成另一个空闲端口时被激活,也可能在此期间另一个进程占用了此端口。实质上,你可能在单个端口上出现竞争条件。

经过进一步研究:

不过有一个小问题。只要本地+远程对是唯一的,两个不同的套接字可以绑定到相同的 IP+端口。我曾经写过一篇关于此的回答。因此,当我使用 :0 创建监听器时,我能够获得一个“冲突”;通过 netstat -an 证明:

10.0.1.11.65245        *.*                    LISTEN
10.0.1.11.65245        17.143.164.101.5223    ESTABLISHED

现在,问题是,如果你想显式绑定套接字正在使用的端口,这是不可能的。可能是因为你只能指定本地地址,而远程地址在调用 listen 或 connect 之前是未知的(我们现在谈论的是系统调用,而不是 Go 接口)。换句话说,当你未指定端口时,操作系统有更多的选择。因此,如果你得到一个本地地址,该地址也被另一个套接字使用,你将无法手动绑定到它。

如何解决:

正如我在评论中提到的,你的服务器进程应该使用 :0 表示法,以便能够从操作系统中选择可用资源。一旦它开始监听,地址应该被通知给感兴趣的进程。你可以通过文件或标准输出来实现这一点。

英文:

What you do is checking whether the port is free at one point and then you try to use it basing on the fact it was free in the past. This is not going to work.

What happens: with every iteration of a for loop, you generate a port number and make sure it's free. Then you spawn a routine with intention of using this port (which is already released back to the pool of free ports). You don't really know when the routine kicks in. It might be activated while the main routine (the for loop) has just generated another free port – maybe the same one again? Or maybe another process has taken this port in a meantime. Essentially you can have a race condition on a single port.

After some more research:

There's a small caveat though. Two different sockets can be bound to the same ip+port as long as the local+remote pair is unique. I've once written a response about it. So when I've been creating listener with :0 I was able to get a "collision"; proved by netstat -an:

10.0.1.11.65245        *.*                    LISTEN
10.0.1.11.65245        17.143.164.101.5223    ESTABLISHED

Now, the thing is that if you want to explicitly bind the socket the port being used, this is not possible. Probably because you would be able to specify local address only and remote address wouldn't be known until call to listen or connect (we're talking about syscalls now, not the Go interface). In other words, when you leave port unspecified, OS has a wider choice. So if it happens you got a local address that is also being used by another socket, you're unable to bind to it manually.

How to sovle it:

As I've mentioned in the comments, your server process should be using :0 notation in order to be able to choose available resource from OS. Once it's listening, the address should be announced to interested processes. You can do it, for example, through a file or a standard output.

答案2

得分: 10

首先我检查端口:

$ lsof -i :8080

结果如下:

COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
WeChat     1527 wonz  241u  IPv4 0xbfe135d7b32e86f1      0t0  TCP wonzdembp:63849->116.128.133.101:http-alt (ESTABLISHED)
__debug_b 41009 wonz    4u  IPv6 0xbfe135e149b1e659      0t0  TCP *:http-alt (LISTEN)

所以我杀掉进程ID:

$ kill 41009

然后它就可以工作了。

英文:

Firstly I check the port:

$ lsof -i :8080

The results are:

COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
WeChat     1527 wonz  241u  IPv4 0xbfe135d7b32e86f1      0t0  TCP wonzdembp:63849-&gt;116.128.133.101:http-alt (ESTABLISHED)
__debug_b 41009 wonz    4u  IPv6 0xbfe135e149b1e659      0t0  TCP *:http-alt (LISTEN)

So I kill PID:

$ kill 41009

Then it works.

答案3

得分: 1

可能你之前在这个端口上运行或调试过一个应用程序,并且它没有正常关闭。该进程可能仍然停留在系统的内存中。完全终止该进程,以及可能潜伏在背景中的任何其他网络守护进程,并尝试重新运行你的应用程序。

如果你还没有检查过这个问题,你可以使用(如果使用Linux)tophtop或任何图形界面系统监视器,如Windows的任务管理器、Gnome3的系统监视器或KDE的KSysGuard来搜索有问题的进程。

举个例子,我观察到Visual Studio Code的调试器/运行器工具(F5/Ctrl+F5)并不总是清理进程,特别是如果你按下F5太快,旧的调试器没有关闭。

英文:

It is possible you were previously running or debugging an application on this port, and it did not shut down cleanly. The process might still be hanging out in your system's memory. Kill that process completely, and any other network daemons that might be lurking in the shadows, and try to run your application again.

If you haven't checked for this already, you can use (if using Linux) top, htop, or any GUI system monitor like Windows' Task Manager, Gnome3's System Monitor or KDE's KSysGuard to search for the offending process.

For an example, I have observed that Visual Studio Code's debugger/runner utility (F5/Ctrl+F5) does not always clean up the process, especially if you hit F5 too quickly and the old debugger did not shut down.

答案4

得分: 0

使用reuseport来有效地使用端口进行监听。

import "github.com/libp2p/go-reuseport"

l, err := reuseport.Listen("tcp", ":"+strconv.Itoa(tcpPort))

// 替代以下代码

l1, err := net.Listen("tcp", ":"+port)

请注意,reuseport是一个Go语言库,用于在多个goroutine之间共享端口。它可以提高网络性能和并发处理能力。

英文:

Use reuseport to afftectively use the port for listen.

"github.com/libp2p/go-reuseport"

l, err := reuseport.Listen("tcp", ":"+strconv.Itoa(tcpPort))

instead of

l1, err := net.Listen("tcp", ":"+port)

huangapple
  • 本文由 发表于 2015年4月29日 00:27:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/29924910.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定