欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

解决JMX 采用hessian协议在NAT网络环境下的通信问题

程序员文章站 2022-07-02 17:54:01
...

       项目中采用了JMX进行客户端与服务端的通信。JMX采用了MX4J。通信协议采用了hessian。但是在NAT的网络环境下,由于JMX agent端(即JMX服务端)采用的是NAT网络内的私有IP,而NAT之外的JMX client端是采用公网IP访问JMX agent.这样访问时,JMX之间的连接建立失败。启动JMX agent后,在外网用agent对应的公网IP及端口进行telnet ,是可以成功的,看来问题出现在JMX建立连接的机制上。

      在网络上查阅相关资料,有讨论解决rmi协议下的NAT网络通信问题。rmi协议下,可采用设置系统变量System.setProperty("java.rmi.server.hostname", your public IP)的方式解决问题。而hessian采用了http协议进行通信,没有走rmi的解决途径。网络上关于JMX 用hessian协议进行NAT网络通信的讨论信息很少。看来只好看源码了。
    MX4J中,当采用hessian协议通信时,MX4J会在内部启动一个jetty的容器,然后将HessianServlet发布到这个容器中。客户端访问时,通过http://IP:port/hessian的 形式的URL访问该servlet。 通过实验,当在NAT网络下部署一个jetty容器并发布后,在外网用公网IP访问目标主机的jetty是可以访问到的,这说明NAT网络本身是可以处理公网IP与私网IP的IP映射的,MX4J建立连接失败应该在于建立连接时 对IP进行的某种校验。
    一步步跟代码,最后发现了问题所在。采用hessian后,启动jmx 服务,MX4J会返回一个HTTPConnectorServer来处理通信问题。该类继承于AbstractJMXConnectorServer。在启动完jetty,发布servlet,创建connectionManager后,HTTPConnectorServer会将该connectionManager注册到一个名为instances的Map中,该value对应的key即为启动时的JMXServiceURL。当客户端访问到来时,HttpService类会根据客户端请求的URL组建一个新的JMXServiceURL,用该URL去instances的MAP中取connectionManager。当NAT网络环境下时,客户端传来的URL请求中的IP是外网公共IP,而注册在Map中的URL里的IP是内部IP。key不一样,自然取不到connectionManager,于是在客户端我们才会收到Could not find ConnectionManager. Make sure a HTTPConnectorServer is in classloader scope and bound at this address的报错。

     问题找到了,我们只要将客户端请求的URL中的IP映射为内网IP,NAT的通信问题即可解决了。可是该如何去实现这样的修改呢。RMI貌似在处理请求时,设置了内外网IP的转换接口,我们才可以修改java.rmi.server.hostname这个系统变量值来实现NAT下不同IP的映射。回归到hessian的代码,并没有看到类似的接口。看来我们只能重新写一套处理新协议的通信机制了。而最终目的就是能把客户端传来的IP替换为内网IP.

 

     在启动jmx时,JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);该方法会根据传入的不同协议,返回不同的JXMConnectorServer.进入到该方法的内部,最后发现,MX4J还是提供了扩展新协议的接口。JMXConnectorServerFactory内 通过ServiceLoader类来动态加载不同协议的ServerProvider。MX4J中,对应每一类通信协议,都定义了一个处理客户端的ClientProvider和处理服务端的ServerProvider。他们负责返回对应的Connector和ConnectorServer。例如我们定义一个新的协议nathessian,那如果希望JMX服务端能够识别该协议,我们需要写一个ServerProvider,该类实现了JMXConnectorServerProvider接口,并返回一个处理通信的JMXConnectorServer实现类。ServiceLoader类规定我们需要在类路径下添加META-INF/services/javax.management.remote.JMXConnectorServerProvide文件,在该文件中,指定我们定义的ServerProvider类的全路径(可查看mx4j的其他协议的这个文件,如hessian,如rmi)。这样,ServerProvider类可通过ClassLoader.getSystemResources(fullName)自动将我们定义的provider加载进来。

    下面如何添加处理nathessian的ConnectorServer。

    MX4J处理hessian的HTTPConnectorServer的doStart方法中,会根据协议类型,获取一个ConnectionResolver。查看mx4j的tools-remote的jar包,每一个基于http的协议下都对应一个Resolver类,该类继承自HTTPResolver,处理相关的创建web容器、部署等事项。子类中的getServletClassName()重写后,负责获取处理hessian请求的HessianServlet类名。这个Servlet会发布到Jetty容器中,通过Servlet里创建的HTTPService处理来自客户端的请求,并调用ConnetorServer里的find的方法,从instances的Map中根据URL获取之前注册的connectionMananger。我们需要自己写一套处理nathessian的ConnectorServer类,Resolver类 ,Servlet类, HTTPService类,分别继承已有的处理hessian的对应的类,只需重写少量几个方法,最终实现在根据URL查询map之前,将客户端的公网IP替换为内网IP;将客户端的hessian协议端替换为我们自定义的nathessian。

     代码都写好后,我们在服务端用nathessian作为协议类型,启动jmx,客户端不需改变,仍用hessian.在NAT环境下测试,OK了,通信正常了。

 

     总结一下,我们在服务端新写了 ServerProvider , HTTPNATConnectorServer, Resolver,NATHessianServlet,NATHTTPService几个子类来处理自定义的nathessian协议