套接字绑定错误

4

我有一个测试应用程序,它打开一个套接字,通过这个套接字发送一些内容,然后关闭它。这样做循环5-10.000次。问题是,在3400次迭代后,我会收到以下类型的错误:

java.net.BindException: Address already in use: connect

我甚至将套接字设置为立即使用,但错误仍然存在。

try
{
     out_server.write(m.ToByteArray());
     socket_server.setReuseAddress(true);
     socket_server.close();
}
catch(Exception e)
{
     e.printStackTrace();
     System.out.println(i+" unable to register with the server");
}

我该怎么做来解决这个问题?
7个回答

11
我认为你可能进行得太快了。
大多数操作系统在任何时候打开的套接字数量都有限制,但情况实际上比这更糟糕。
当一个套接字被关闭时,它会被放置在特殊的等待状态中,持续一定的时间。通常是两倍的数据包生存时间,并确保网络上没有仍在传输到您的套接字的数据包。
一旦该时间过期,您可以确信所有在网络中的数据包已经死亡。套接字被放置在特殊状态下,以便在关闭时在网络中的数据包可以被捕获并在其死亡之前到达时被丢弃。
我认为这就是你的情况,套接字没有像你想象得那么快地释放。
我们在代码中打开了许多短暂的会话,遇到类似的问题。它运行得很好,但随着硬件变得更快,可以在给定时间内打开更多会话。这表现为无法打开更多会话。
检查这一点的一种方法是从命令行执行 `netstat -a`,看看有多少会话实际上处于等待状态。
如果事实证明确实如此,有几种处理方法:
- 重用您的会话,手动或通过维护连接池。 - 在每个连接中引入延迟,以尝试阻止达到饱和点。 - 先全力以赴,直到达到饱和状态,然后再修改您的行为,例如将连接逻辑运行在重试最多 60 次、每次延迟两秒钟的 while 语句内。这使您可以以最大速度运行,仅在出现问题时降低速度。
最后一个项目需要一些扩展。在我们上述应用程序中,实际上使用了后退策略,如果资源提供者抱怨,它会逐渐减轻对该资源的负载,因此我们选择了一个一秒钟,然后两秒钟,然后四秒钟等的时间间隔,而不是 30 个两秒钟的延迟。
后退策略的常规过程如下,它可以用于任何可能存在资源临时短缺的情况。伪代码中暗示的操作将是在您的情况下打开套接字。
set maxdelay to 16 # maximum time period between attempts
set maxtries to 10 # maximum attempts

set delay to 0
set tries to 0
while more actions needed:
    if delay is not 0:
        sleep delay
    attempt action
    if action failed:
        add 1 to tries
        if tries is greater than maxtries:
           exit with permanent error
        if delay is 0:
            set delay to 1
        else:
            double delay
            if delay is greater than maxdelay:
                set delay to maxdelay
    else:
        set delay to 0
        set tries to 0

这样做可以让进程在绝大多数情况下以最大速度运行,但当错误开始发生时会逐渐退避,希望能给资源提供者恢复的时间。延迟的逐步增加允许更严重的资源限制得到恢复,而最大尝试次数则捕获您所称的永久性错误(或需要过长时间才能恢复的错误)。


你需要根据连接的目标机器的操作系统,来配置套接字的time_wait以及其他相关参数。 - Ryan Fernandes
2
不总是一个好主意去调整这些参数。大多数应该针对网络特性进行调整,然后再调整您的应用程序。如果减少time_wait而不减少time-to-live,则会导致虚假数据包到达。将TTL降低到数据包无法到达目的地的程度意味着会有很多丢失的数据包。理想情况下,您应该保持连接打开(手动或使用连接池),或者调整您的应用程序行为(例如使用@Stu在他的答案中提到的延迟)。 - paxdiablo

2

我的建议:

  • 在写入后刷新套接字
  • 在上述方法结束时添加一个小的睡眠时间(~50ms?)

@Pax关于套接字状态的观点很好。尝试运行您的代码,让它失败,然后执行netstat并分析它(或在此处发布)


其实我有睡眠时间,但只有5毫秒。 - klaus johan

2
什么操作系统?如果您使用的是Windows,我猜想是这样的,那么您可以拥有的客户端连接数量是有限制的(这由MaxUserPort注册表项配置,其默认值为4000;请参见http://technet.microsoft.com/en-us/library/aa995661.aspxhttp://www.tech-archive.net/Archive/Windows/microsoft.public.windows.server.general/2008-09/msg00611.html了解更改详细信息)。这个问题与您从客户端启动套接字关闭并在客户端累积处于TIME_WAIT状态的套接字有关,可能是您出现问题的原因。
请注意,解决TIME_WAIT累积问题的方法不是调整TCP堆栈的参数以消除问题。TIME_WAIT存在有很好的理由,删除或缩短它可能会导致新的问题!
所以,假设您正在使用Windows机器,步骤一是调整MaxUserPort值,以便为出站连接提供更多动态端口。接下来,如果这不能解决问题,您可以考虑哪一端的连接应该结束TIME_WAIT(假设您可以控制连接所使用的协议...)。发出“主动关闭”的对等方将最终结束TIME_WAIT,因此如果您可以更改服务器发出主动关闭,则TIME_WAIT套接字将在服务器上累积而不是客户端上,这可能对您有所帮助...

确实,我正在使用Windows XP。我不知道传入连接的限制是多少,但如果与传出连接相同,那么我只是将问题转移到了服务器上。不过,我认为最大化传出端口是一个好的方法。谢谢! - klaus johan
入站连接没有相应的限制。 - Len Holgate
请注意,此功能仅适用于Windows客户端通过XP和服务器通过2003。Vista和Server 2008及以上版本使用新的TCP/IP实现,您需要使用netsh interface ipv4 show dynamicportrange tcp进行显示,并使用相应的“set”命令来更改这些值。我手头上有一台Windows 7企业计算机,默认情况下有16384个动态端口可用,基本消除了这种限制。 - Will Angley

1

我同意其他人的看法,你的套接字端点已经用尽。然而,从你的示例中并不完全清楚,因为异常可能来自于 connect() 或 bind() 调用,这些调用可能是某个其他高级 Java 方法的基础。

还应该强调的是,套接字库的端点用尽并不是一种奇怪的限制,而是任何 TCP/IP 实现的一个非常基本的部分。你需要保留有关旧连接的信息一段时间,以便丢弃针对旧连接的迟到 IP 数据包。

setReuseAddress() 对应于低级别的 SO_REUSEADDR 套接字选项,仅适用于服务器在执行 listen() 时。


1
SO_REUSEADDR套接字选项仅适用于服务器是一个错误的说法。此选项可由服务器和客户端使用。 - Aditya Sehgal

0

有时在使用socket.close()之后,套接字不会立即关闭,而是继续执行循环(在循环中,它尝试与相同的ip和端口建立socket连接),因此请将socket设置为null。

socket_server.close();

socket_server = null;

谢谢 Sunil Kumar Sahoo


0

我认为这与这个问题相同(我已经链接到我的答案,我认为可能会有所帮助。)

Java绑定异常


0

如果示例代码实际上是您执行循环的方式,那么您可能顺序有误。

setReuseAddress的Java文档说:在套接字绑定(请参考isBound())后启用或禁用SO_REUSEADDR时的行为未定义。

尝试将调用移动到bind()或connect()之前的某个地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接