linux之net.DialTCP 在 Linux 上产生 "connection refused"错误,但在 Windows 上没有
代码
重现需要两个应用程序运行并通过 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.exe和
clientB.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 已实现(仅通过手动或从同一脚本运行两个客户端几乎无法完成;即使在同一进程和线程中进行模拟也不是那么容易)。
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。



