Zeroc Ice中间件之通讯器资源回收

引言:

    一个类的方法非常多,且被调用较多时,就不宜采用“单方法单代理”的方式了来调用远程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的理解,欢迎大家批评、讨论。

(完)

猜你喜欢

转载自zhaoningbo.iteye.com/blog/1087853