引言:
一个类的方法非常多,且被调用较多时,就不宜采用“单方法单代理”的方式了来调用远程ICE服务了。其本质原因是,客户端每次申请新代理都会占用本机的一个端口。如果“单方法单代理”遭遇峰值,可能会直接导致本机端口原因引发异常。异常内容如下:
41:30,031:[CCX_S_FA_INVESTINCOME]002143 Exception in thread "main" Ice.SyscallException error = 0 at IceInternal.Selector.<init>(Selector.java:29) at IceInternal.ThreadPool.<init>(ThreadPool.java:77) at IceInternal.Instance.finishSetup(Instance.java:786) at Ice.CommunicatorI.finishSetup(CommunicatorI.java:286) at Ice.Util.initialize(Util.java:212) at Ice.Util.initialize(Util.java:222) at com.ccx.stock.financialAnalysisNew.DerRepoDataClient.getOwnPrx(DerRepoDataClient.java:83) at com.ccx.stock.financialAnalysisNew.DerRepoDataClient.CCX_S_FA_INVESTINCOME(DerRepoDataClient.java:204) at com.ccx.stock.financialAnalysisNew.TestDerRepoDataClient.main(TestDerRepoDataClient.java:44) Caused by: java.io.IOException: Unable to establish loopback connection at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:106) at java.security.AccessController.doPrivileged(Native Method) at sun.nio.ch.PipeImpl.<init>(PipeImpl.java:122) at sun.nio.ch.SelectorProviderImpl.openPipe(SelectorProviderImpl.java:27) at java.nio.channels.Pipe.open(Pipe.java:133) at sun.nio.ch.WindowsSelectorImpl.<init>(WindowsSelectorImpl.java:104) at sun.nio.ch.WindowsSelectorProvider.openSelector(WindowsSelectorProvider.java:26) at java.nio.channels.Selector.open(Selector.java:209) at IceInternal.Selector.<init>(Selector.java:25) ... 8 more Caused by: java.net.BindException: Address already in use: connect at sun.nio.ch.Net.connect(Native Method) at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:507) at java.nio.channels.SocketChannel.open(SocketChannel.java:146) at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:78) ... 16 more
下文将对此问题做以简单介绍。
正文:
(晚点续上)
(续上)
1,首先有以下几个要点需要说明。
A)项目中ICE通讯使用的是默认TPC/IP(socket)通讯;
B)Java1.4后以NIO升级老版IO(以支持多路复用);
C)请求发出后,返回之前,请求者需要被阻塞(java Object.wait()等及线程机制的需要);
2,接下来说说引言中提到的报错信息。
第2行:Ice.SyscallException,ICE骨架支持异常。
第13行:Unable to establish loopback connection,本机对本机的连接不能持久化。
第24行BindException: Address already in use: connect,地址已经被占用。
3,详细分析。
本例异常,引发的根本原因是13行与24行。
24行的异常。主要是由于Java每个socket请求时,都会占用*至少*一个端口,本例测试时循环5000个socket请求,在400左右时便出现上述异常。其原因就在当前请求的socket端口已被之前的socket占有。关于此异常很常见,随便搜索一下,可以看到大量的说明,因此这里跳过。
13行的异常。也是本例异常产生的重点。
loopback connection,这是什么connection呢?!是形如如下的connection:
TCP 10.0.2.128:3024 10.0.2.128:3046 ESTABLISHED TCP 10.0.2.128:3046 10.0.2.128:3024 ESTABLISHED
这两行TCP连接互相监听,组成一对“loopback connection”。初看此连接觉得很费解,为什么会出现这样的TCP(socket)连接呢?回想一下正文第1节C中提到的“阻塞-唤醒”这一需要。我们假设有一个名A的socket请求发出后,很自然进入阻塞状态,随后服务者B在处理结束(正常、异常、强制),想以更效率的DI(Donot call me, I'll call U.)方式唤醒请求者A进行后续处理。
这时就遇到一个问题DI的唤醒如何实现。可能的方式有三:一是B处理结束后有数据需要处理或发生异常;二是超时timeout;三是强制方式(如Executors强制限时完成等)。一看就只有第一种方式了。让自己的3024监听自己的3046,如果自己的socket请求正常调用就不说了;非正常情况下,只要把异常处理或者超时处理交给3046来处理并返回给3024,通知失败(有返回数据需要处理时可被自动唤配),随后自己即可被唤醒了。
这样也就解释了为什么会有loopback connection异常发生。
另外,需要说明的是ICE穿墙采用的是“双向连接”方式,每个客户端请求可能需要使用不止一两个端口。因此在客户端编写并发、单请求单通路,这类socket短时峰值较高的代码时,应该尽力避免。
总结:
1,由于java socket连接本身存在“对端口资源的消耗性 与 端口资源的有限性”这一问题,加之ICE默认TCP双向连接的特点更加强了消耗性影响,因此在使用ICE创建通讯器请求远程代理时,尽可能加强代理“复用性”,减少“回收与重申请”。
2,ICE是主要是用于企业架构的,而非通信的。如果不是为架构而来,建议可以考虑一下mina——更专业的java通信框架。
尾声:
因为笔者是C/C++,Java双修,双“半吊子”,对socket的理解,欢迎大家批评、讨论。
(完)