今天运行了写好的程序,出现了错误。java.net.BindException: Address in use: connect。查找网上资源,说主要原因是因为连接太多,socket绑定端口在短时间内不能释放。
java.net.ConnectException: Address already in use
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:305)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:171)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:158)
at java.net.Socket.connect(Socket.java:426)
at com.nec.adams.app.mp.ups.Session.setup(Session.java:383)
at com.nec.adams.app.mp.ups.StateActive.run(StateActive.java:197)
at com.nec.adams.app.mp.ups.UPSServer.runService(UPSServer.java:248)
at com.nec.adams.app.mp.ups.UPSServer.main(UPSServer.java:155)
程序的流程如下:
socket = new Socket();
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress(127.0.0.1, 19760));
socket.connect(new InetSocketAddress(192.168.0.5, 5111), 1000);
最开始的时候,没有使用"socket.setReuseAddress(true);"这句,出现的是
"java.net.BindException: Address already in
use"。仔细分析,引来对socket的bind和connect的调查。
普通情况下的socket关闭,两个连接端点都需要发送FIN (final)
包,并且两个端点都应该回应ACK
(acknowledge)对方的FIN包,然后socket关闭完成。FIN包由应用程序的close(),shutdown(),exit()这些发
起,而ACK包在close()完成后由系统内核发送。
上图显示了所有可能的正常关闭情况,根据事情发生的不同顺序。注意到如果你发起关闭,另一端会出现一个“TIME_WAIT"的状态。”
TIME_OUT"状态在这个过程完成后会帮定这个port几分鈡。具体timeout的时间根据不同的操作系统而定。不过典型的时间是1到4分钟。
为了避免绑定失败,可以使用setReuseAddress(true)方法,这样系统允许一个进程绑定哪怕是处在TIME_WAIT状态的端口。这是最简单有效的消除“Address already in use"错误的方法。
奇怪的是,这样又带来更复杂的问题。setReuseAddress(true)允许你用一个正在TIME_WAIT的端口,但是你仍然不能与上次你连接
的地址建立连接。什麽?假设我选择了端口1010,去连接foobar.com的端口300,然后在本地关闭,让那个端口处在TIME_WAIT状态。这
个时候我立刻可以用端口1010去连接除了foobar.com的端口300。在这个时候出现的就是
"java.net.ConnectException:Address already in
use"。在这个时候使用setReuseAddress(true)是无效的,应该避免。
有些人不喜欢使用setReuseAddress(true)的另外一个原因是它带来一个安全问题。在一些操作系统上它允许不同的进程同时使用相同的端口
区连接不同的地址。这是一个问题,因为大多数的服务器绑定在这个端口上,但是它并没有帮定到一个地址(这就是为什麽netstat的输出会出现
*.8080这种情况)。於是如果这个服务器绑定*.8080,另外一个恶意的用户能够帮定local-machine.8080,去窃听你的连接的特定
信息。
通过上面的图,可以看到TIME_WAIT可以被避免,如果远程端口发起关闭。如是服务器能避免这个问题,通过让客户端首先关闭连接。应用层协议必须被设
计成客户端知道什麽时候关闭连接。服务器端能完全地关闭,通过客户端的回应EOF。
但是,它仍然需要设置一个timeout,如果客户端不正常断开了网络。在多数情况下在服务器关闭之前等上几秒就足够了。
参考文章:
http://hea-www.harvard.edu/~fine/Tech/addrinuse.html
在读了W. Richard Stevens 的《TCP/IP Illustated》之后,才明白,为什么会有这个TIME_WAIT状态,和为什么等待的时间是大约4分钟,而且各个系统不一样。
2MSL连接
TIME_WAIT状态也称为2MSL等待状态。每个TCP必须选择一个报文段最大生存时间MSL(Maximun Segment
Lifetime)。它是任何报文段被丢弃前在网络的最长时间。RFC 793(Postel
1981c)指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的Socket(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能被使用。
遗憾的是,大多数的TCP实现(如柏克利版)强加了更为严格的限制。在2MSL等待期间,Socket中使用的本地端口在默认情况下不能被再次使用。
某些实现和API提供了一种避开这个限制的方法。使用Socket API时,可说明其中的SO_REUSEADDR选项。它可让调用者对处于2MSL等待的本地端口进行赋值,但我们将看到TCP原则上仍将避免使用处于2MSL连接中的端口。
一个Socket对(即包含本地IP地址、本地端口、远端IP地址、远端端口的4元组)在它处于2MSL等待时,将不能再被使用。尽管许多具体的实现中允
许一个进程重新使用仍处于2MSL等待的端口(通常是设置选项SO_REUSEADDR),但TCP不能允许一个新的连接建立在相同的Socket对上。