XML-RPC

XML-RPC协议: http://xmlrpc.scripting.com/spec.html
XML-RPC实现: http://ws.apache.org/xmlrpc/

从基础协议来看,主要支持以下类型:
Integer, Boolean, String, Double, Date, Base64, Array, Map,Exception(仅error code and message)

apache xmlrpc实现做了对Java对象序列化以及异常堆栈的扩展,但使用之前客户端与服务端都必须启用这些扩展,任意一端使用了这些功能但未开启均会抛异常。

代码配置:
config.setEnabledForExtensions(true);
config.setEnabledForExceptions(true);

服务端:
提供了用于测试的web server实现: org.apache.xmlrpc.webserver.WebServer
只不过一般都是通过servlet的形式部署在成熟的web server上,但有意思地是我们必须把服务注册文件放在classpath
org/apache/xmlrpc/webserver/XmlRpcServlet.properties下面。

详细请见官方文档: http://ws.apache.org/xmlrpc/server.html

客户端:
默认客户端使用的 org.apache.xmlrpc.client.XmlRpcSunHttpTransportFactory进行连接,也就是 java.net.HttpURLConnection,这个开启的连接用完之后就会主动关闭且不提供重试功能。

还有一个就是用commons-httpclient的 org.apache.commons.httpclient.HttpClient.XmlRpcCommonsTransportFactory
但是默认情况下每次调用,它会新生成一个HttpClient对象,可以通过从外部传入一个HttpClient对象来进行重用。

再者HttpClient默认情况又使用的是单线程,单连接的连接管理器: org.apache.commons.httpclient.SimpleHttpConnectionManager
这时需要使用多线程,多连接版本: org.apache.commons.httpclient.MultiThreadedHttpConnectionManager

以下是一段示例代码:
//多线程版本
		MultiThreadedHttpConnectionManager connManager = new MultiThreadedHttpConnectionManager();
		//最大连接数设为10
		connManager.getParams().setDefaultMaxConnectionsPerHost(10);		
		HttpClient httpClient = new HttpClient(connManager);		
		
		XmlRpcClient rpcClient = new XmlRpcClient();	
		XmlRpcCommonsTransportFactory transportFactory = new XmlRpcCommonsTransportFactory(rpcClient);	
		//重用HttpClient对象
		transportFactory.setHttpClient(httpClient);		
		rpcClient.setTransportFactory(transportFactory);
		
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		config.setServerURL(new URL("http://192.168.1.6:9000/test/xmlrpc"));
		//开启扩展功能以支持Java复杂对象序列化
		config.setEnabledForExtensions(true);
		rpcClient.setConfig(config);
		
		//使用对象代理,看起来更像RPC点
		ClientFactory factory = new ClientFactory(rpcClient);
		Calculator calc = (Calculator) factory.newInstance(Thread.currentThread().getContextClassLoader(), 
				Calculator.class, "Calculator"); //可以指定绑定的服务名,不然默认提交的服务名将是接口的全类名


这里顺带提下commons-httpclient-3.x的连接池以及重试机制。

HttpClient默认情况下每次从连接池拿到一个连接会进行连接测试,以确保拿到的连接是可用的,未关闭的。

一般的HttpServer实现即使客户端使用HTTP1.1要求Keep-Alive的Connection,也会在一定时间没有数据交互后 主动关闭连接,Tomcat6.0.18直接使用socket的connectionTimeout的设置默认为20秒,你可以在apache-tomcat-6.0.18\conf\server.xml的Connector配置项进行修改测试。

你可以通过以下方式禁用使用连接前测试。
httpClient.getParams().setParameter("http.connection.stalecheck", false);

但禁用之后,会发生什么状况呢?
HttpClient会直接使用此连接发送请求,并试图读取响应。
从TCP协议来看,Http Server的主动关闭只是关闭了 server -> client的连接,client仍然可以发送数据给server,但是client将读不到数据。

测试发现如果先client发送数据给server,然后试图读取响应,会抛出如下异常。
java.net.SocketException: Software caused connection abort: recv failed

而如果不先client发送数据给server,试图读取响应可以得到-1这种EOF连接关闭提示。

恰巧HttpClient默认重试机制将不给与机会给已成功发送数据,却返回java.net.SocketException的情况进行重试,具体可以参考: org.apache.commons.httpclient.DefaultHttpMethodRetryHandler.retryMethod

这样如果关闭了连接前测试,将可能会出现调用失败的情况。

关于连接前测试,我们可以参考下HttpClient的代码,使用临时设置socket timeout为1以及BufferedInputStream的mark和reset,这样让读取尝试不会消费掉本应让应用读到的数据。

org.apache.commons.httpclient.HttpConnection.isStale()

try {
                        socket.setSoTimeout(1);
                        inputStream.mark(1);
                        int byteRead = inputStream.read();
                        if (byteRead == -1) {
                            // again - if the socket is reporting all data read,
                            // probably stale
                            isStale = true;
                        } else {
                            inputStream.reset();
                        }
                    } finally {
                        socket.setSoTimeout(this.params.getSoTimeout());
                    }


NIO就可以很方便地通过数据读取事件检测到连接关闭,只不过要记得在处理中关闭此连接哦,不然它会不耐其烦地告诉你有数据可读,其实就是一个无聊的-1。

Http Server的主动关闭,如果client未进行相应地close,会造成一大堆长时间的 CLOSE_WAIT状态的TCP连接,比 TIME_WAIT可要久多了,这样也会浪费掉client端的套接字资源。

猜你喜欢

转载自steven2011.iteye.com/blog/1310420