BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

分析下载HTML页面出错!!

Posted on 2007-11-09 01:49 dybjsun 阅读(1421) 评论(1)  编辑  收藏 所属分类: 技术随笔

      今天运行了写好的程序,出现了错误。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对上。


评论

# re: 分析下载HTML页面出错!!  回复  更多评论   

2007-12-02 12:11 by web
在任务管理器中把相关的进程关闭,然后在重新启动tomcat

只有注册用户登录后才能发表评论。


网站导航: