linux之net.DialTCP 在 Linux 上产生 "connection refused"错误,但在 Windows 上没有

98°冷暖 阅读:171 2025-06-02 22:19:02 评论:0

代码

重现需要两个应用程序运行并通过 TCP 相互连接。因此,我制作了一个包含 powershell 构建脚本的小型仓库。 link to the full repo

但是为了避免额外的点击,这里是 clientA.go 的代码.

package main 
 
import ( 
    "fmt" 
    "net" 
    "time" 
) 
 
func main() { 
    clientA, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf(":%v", "2222")) 
    if err != nil { 
        fmt.Println(err) 
        return 
    } 
 
    clientB, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf(":%v", "3333")) 
    if err != nil { 
        fmt.Println(err) 
        return 
    } 
 
    for { 
        clientAtoB, err := net.DialTCP("tcp4", clientA, clientB) 
        if err != nil { 
            fmt.Println(err) 
        } else { 
            defer clientAtoB.Close() 
            clientAtoB.SetLinger(0) 
            clientAtoB.SetNoDelay(true) 
            clientAtoB.SetKeepAlive(false) 
            fmt.Println("connected as Client A!") 
            buffer := make([]byte, 64) 
            _, err = clientAtoB.Read(buffer) 
            if err != nil { 
                continue 
            } 
        } 
        time.Sleep(time.Second) 
    } 
} 
clientB.go 的代码除了交换本地和远程端点外是相同的:
clientBtoA, err := net.DialTCP("tcp4", clientB, clientA)
问题

我为 Windows 和 Linux 构建了相同的 go 代码,但在运行时应用程序会产生不同的结果。特别是如何在每个平台上调用 TCP 连接。

在 Windows 上,当我运行两个可执行文件时 clientA.execlientB.exe (从 build.ps1 脚本构建)我得到了想要的结果。如此屏幕截图所示:


但是,当我上传并执行 Linux 二进制文件时,结果是不同的:
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ sudo chmod +x clientA clientB 
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ ls -la 
total 10984 
drwxrwxr-x 3 ubuntu ubuntu    4096 Apr 27 03:09 . 
drwxrwxr-x 4 ubuntu ubuntu    4096 Apr 27 03:08 .. 
drwxrwxr-x 8 ubuntu ubuntu    4096 Apr 27 03:08 .git 
-rw-rw-r-- 1 ubuntu ubuntu   11255 Apr 27 03:12 A.txt 
-rw-rw-r-- 1 ubuntu ubuntu   11255 Apr 27 03:12 B.txt 
-rw-rw-r-- 1 ubuntu ubuntu     247 Apr 27 03:08 build.ps1 
-rwxrwxr-x 1 ubuntu ubuntu 2950662 Apr 27 03:08 clientA 
-rw-rw-r-- 1 ubuntu ubuntu 2642944 Apr 27 03:08 clientA.exe 
-rw-rw-r-- 1 ubuntu ubuntu     718 Apr 27 03:08 clientA.go 
-rwxrwxr-x 1 ubuntu ubuntu 2950662 Apr 27 03:08 clientB 
-rw-rw-r-- 1 ubuntu ubuntu 2642944 Apr 27 03:08 clientB.exe 
-rw-rw-r-- 1 ubuntu ubuntu     718 Apr 27 03:08 clientB.go 
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ ./clientA > A.txt & ./clientB > B.txt & 
[1] 24914 
[2] 24915 
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ cat A.txt 
dial tcp4 :2222->:3333: connect: connection refused 
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$ cat B.txt 
dial tcp4 :3333->:2222: connect: connection refused 
ubuntu@ip-172-31-16-224:~/go/src/github.com/fanmanpro/dial-vs-listen$                                                

我不希望 connection refused错误,因为这两个应用程序在同一环境下运行,因此没有防火墙生效,并且权限相同。

无论平台如何,我怎样才能获得相同的结果?或者为什么它们首先不同?

编辑

Windows 上的成功连接不仅仅是时机的幸运。在 Windows 上,我可以运行 A 5 分钟,然后当我运行 B 时,两者都连接成功。

更新 (2020-04-27)

在收到 Go 开发人员的反馈后,我被告知这可能是 Linux 配置问题,而不是 Go 特有的问题。除了权限之外,我无法阻止同一环境中的两个应用程序建立这样的 TCP 连接? (这些低级 Linux 的东西并不是我的强项。)

请您参考如下方法:

为什么这在 Linux 上不起作用是很明显的。 A 和 B 都是连接到需要监听的对方的客户端。在 Linux(或 UNIX)上,如果您尝试运行 ClientA,它将尝试拨入 ClientB 的地址和端口。如果 没有进程已经在监听这个地址和端口来接受连接在那一刻,ClientA 将立即以 connection refused 结束。错误(这并不完全正确,但大多数情况下,请参阅答案末尾的我的编辑)。

在 Windows 上,Golang 底层使用(用于 tcp、tcp4 和 tcp6 协议(protocol)) ConnectEx 用于面向连接的套接字的 API。此 API 的行为不同于 Linux connect API。如果 ConnectEx无法立即连接它返回错误代码 ERROR_IO_PENDING并且在幕后操作系统等待/重试,直到连接被接受并建立(或者它放弃并使其最终失败)然后通知回来 - 这称为重叠 I/O。

MSDN ConnectEx 文档的相关部分:

Connection-oriented sockets are often unable to complete their connection immediately, and therefore the operation is initiated and the function immediately returns with the ERROR_IO_PENDING or WSA_IO_PENDING error. When the connect operation completes and success or failure is achieved, status is reported using the completion notification mechanism indicated in lpOverlapped.



现在,您在 Windows 上的情况是您尝试 ConnectEx从双方和操作系统为您连接这些套接字。这仅在另一端在一定时间内连接时才有效。如果尝试合理增加 time.Sleep两个客户端的时间间隔(例如 17 和 28),即使在 Windows 上,您也可以看到它们将很难再连接。

对您的问题的回答是,您现在编写的代码取决于 Windows 上 Golang 中 TCP 拨号的操作系统特定行为,并且不可移植。 要将您的软件修复为在 Golang 支持的任何平台上可移植,您可能需要更改逻辑,以便 ClientA 和 ClientB 用于传入连接,并定期尝试 连接 到对面。

编辑 : 我并不是说你的代码根本不能在 Linux 上运行。它实际上使用称为 TCP 同时连接的罕见连接模式,您可以在其中连接两个进程而无需其中任何一个 listen .拨号双方同时发送他们的 SYN,因此每一方都以 SYN/ACK 和 ACK 响应,以完成 3 次握手和 ESTABLISH 连接。这需要非常精确的定时和同步 connect调用两个客户。如果 Linux 内核中允许 TCP 同时连接并且在 connect 之间同步,则双方将连接s 已实现(仅通过手动或从同一脚本运行两个客户端几乎无法完成;即使在同一进程和线程中进行模拟也不是那么容易)。


标签:linux
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号