为啥 connect() 会给出 EADDRNOTAVAIL?

Posted

技术标签:

【中文标题】为啥 connect() 会给出 EADDRNOTAVAIL?【英文标题】:Why would connect() give EADDRNOTAVAIL?为什么 connect() 会给出 EADDRNOTAVAIL? 【发布时间】:2011-04-22 15:19:48 【问题描述】:

我的应用程序出现了一个似乎无法重现的故障。我有一个失败的 TCP 套接字连接,应用程序试图重新连接它。在尝试重新连接的第二次 connect() 调用中,我得到一个错误结果,错误结果为 errno == EADDRNOTAVAIL,connect() 的手册页表示:“本地计算机无法使用指定的地址。”

查看对connect()的调用,第二个参数似乎是错误所指的地址,但据我了解,这个参数是远程主机的TCP套接字地址,所以我很困惑关于引用本地机器的手册页。是不是我的本地机器上没有这个远程 TCP 套接字主机的地址?如果是这样,为什么会这样?它必须在连接失败之前第一次成功调用 connect() 并尝试重新连接并收到此错误。两次 connect() 的参数都是相同的。

如果我再次尝试调用 connect,如果我等待足够长的时间,这个错误是否会消失?如果不是,我应该如何尝试从这个失败中恢复?

【问题讨论】:

我在大型 Redis 集群中遇到了类似的问题。你的用例是什么? 【参考方案1】:

查看此链接

http://www.toptip.ca/2010/02/linux-eaddrnotavail-address-not.html

编辑:是的,我本来想添加更多,但由于紧急情况不得不删掉它

您在尝试重新连接之前是否关闭了套接字?关闭将告诉系统套接字对(ip/port)现在是空闲的。

这里还有一些额外的项目:

如果本地端口已经连接到给定的远程 IP 和端口(即,已经有相同的套接字对),您将收到此错误(请参阅下面的错误链接)。 绑定非本地套接字地址会产生此错误。如果机器的 IP 地址是 127.0.0.1 和 1.2.3.4,并且您尝试绑定到 1.2.3.5,则会出现此错误。 EADDRNOTAVAIL:指定的地址在远程机器上不可用或名称结构的地址字段全为零。

链接到与你类似的错误(答案接近底部)

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4294599

您的套接字似乎基本上停留在 TCP 内部状态之一,添加重新连接延迟可能会解决您的问题,正如他们在该错误报告中所做的那样。

【讨论】:

【参考方案2】:

如果给定的端口无效,例如 0,也会发生这种情况。

【讨论】:

作为目标端口。如果它作为要绑定的本地端口提供,则它是有效的。【参考方案3】:

如果您不愿意更改可用的临时端口数量(如 David 所建议的那样),或者您需要的连接数超过了理论最大值,还有其他两种方法可以减少正在使用的端口数量。但是,它们在不同程度上违反了 TCP 标准,因此应谨慎使用。

第一种是开启SO_LINGER,超时时间为零,强制TCP堆栈发送RST数据包并刷新连接状态。但是有一个微妙之处:您应该在close 之前调用套接字文件描述符上的shutdown,这样您就有机会在RST 数据包之前发送FIN 数据包。所以代码看起来像:

shutdown(fd, SHUT_RDWR);
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 0;
// todo: test for error
setsockopt(fd, SOL_SOCKET, SO_LINGER,
           (char *) &linger, sizeof(linger));
close(fd);

如果FIN 数据包与RST 数据包重新排序,服务器应该只会看到过早的连接重置。

请参阅TCP option SO_LINGER (zero) - when it's required 了解更多详情。 (在实验上,setsockopt 的设置位置似乎并不重要。)

第二个是使用SO_REUSEADDR 和显式bind(即使您是客户端),这将允许Linux 在您运行时重用临时端口,然后再等待。请注意,您必须bindINADDR_ANY 和端口0 一起使用,否则SO_REUSEADDR 将不受尊重。您的代码将类似于:

int opts = 1;
// todo: test for error
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
         (char *) &opts, sizeof(int));

struct sockaddr_in listen_addr;
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = 0;
listen_addr.sin_addr.s_addr = INADDR_ANY;
// todo: test for error
bind(fd, (struct sockaddr *) &listen_addr, sizeof(listen_addr));

// todo: test for addr
// saddr is the struct sockaddr_in you're connecting to
connect(fd, (struct sockaddr *) &saddr, sizeof(saddr));

这个选项不太好,因为您仍然会根据netstat -an | grep -e tcp -e udp | wc -l 使 TCP 连接的内部内核数据结构饱和。但是,在这种情况发生之前,您不会开始重用端口。

【讨论】:

SO_LINGER 设置为零解决了我的问题。谢谢。【参考方案4】:

我遇到了这个问题。我通过启用 tcp 时间戳解决了它。

根本原因:

    连接关闭后,连接将进入 TIME_WAIT 状态一段时间 时间。

    在此状态下,如果任何新连接具有相同的 IP 和 PORT, 如果在创建套接字期间未提供 SO_REUSEADDR,则套接字 bind() 将失败并出现错误 EADDRINUSE。

    但是即使在提供 SO_REUSEADDR 之后 sockect connect() 也可能 如果双方均未启用 tcp 时间戳,则失败并返回错误 EADDRNOTAVAIL。

解决方案: 请在客户端和服务器端启用 tcp 时间戳。

echo 1 > /proc/sys/net/ipv4/tcp_timestamps

启用 tcp_timestamp 的原因:

当我们启用 tcp_tw_reuse 时,处于 TIME_WAIT 状态的套接字可以在过期之前被使用,内核会尝试确保没有关于 TCP 序列号的冲突。如果我们启用 tcp_timestamps,它将确保不会发生这些冲突。但是,我们需要在两端启用 TCP 时间戳。血淋淋的细节见 tcp_twsk_unique 的定义。

参考: https://serverfault.com/questions/342741/what-are-the-ramifications-of-setting-tcp-tw-recycle-reuse-to-1

【讨论】:

【参考方案5】:

要检查的另一件事是接口是否已启动。我最近在使用网络命名空间时对此感到困惑,因为它似乎创建一个新的网络命名空间会产生一个完全独立的环回接口,但不会带来它(至少,对于 Debian wheezy 的事物版本)。这让我有一段时间没有想到,因为人们通常不会认为环回是关闭的。

【讨论】:

以上是关于为啥 connect() 会给出 EADDRNOTAVAIL?的主要内容,如果未能解决你的问题,请参考以下文章

为啥即使我传递了错误的用户名,mysqli_connect() 也会返回 true?

为啥 withRouter 与 connect() 一起使用会破坏我的反应应用程序?

为啥 iTunes Connect 有时会为内部测试人员提供“邀请测试人员”选项?

为啥我在AppGallery Connect中为其他应用更改货币时,我的应用中的应用内产品价格的货币会自动更改?

请给出TCP connect()这个函数的所有参数及其解释?

为啥哈希函数会给出分段错误?