java.net.URLConnection发送HTTP请求与通过Apache HttpClient发送HTTP请求比较

        由于前段时间对java.net.URLConnection和org.apache.http.impl.client.HttpClients发送HTTP请求有所接触,发现在使用过程中碰到一些不一样的地方,特简单记录下来,以免忘记。

        如果要说它们的区别,其实用过的人都知道,Java有原生的API可用于发送HTTP请求,即java.net.URL、java.net.URLConnection,这些API很好用、很常用,但不够简便;所以,才流行有许多Java HTTP请求的框架,如Apache的HttpClient。由此可见,Apache的HttpClient使用简便。

        但我今天主要要说说我在使用过程中遇到的——java.net.URLConnection是请求间隔小于等于5秒,则不会新建TCP连接;如请求间隔大于5秒,则每次请求才会新建TCP连接,而Apache HttpClient在任何情况下都会新建TCP连接的问题现象及分析过程。

一.现象

        为了证明这点,我特意新建了一个小工程(如下所示),向我本地的Web Application发送请求:


        其中HttpRequestor.java就是从通过java.net.URLConnection发送HTTP请求中拷过来的。

ApacheClient.java

package com.bijian.study.http;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ApacheClient {

	private static final Logger logger = LoggerFactory.getLogger(ApacheClient.class);
	
	public static void httpRequest() throws Exception {
		
		CloseableHttpClient httpclient = HttpClients.createDefault();
		HttpGet httpget = new HttpGet("http://10.38.67.108:8080/SpringMVC/greeting");
		HttpResponse response = httpclient.execute(httpget);
		HttpEntity entity = response.getEntity();
		String html = EntityUtils.toString(entity);
		httpclient.close();

		logger.info(html.replaceAll("\r\n", ""));
	}
}

JavaHttpClient.java

package com.bijian.study.http;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bijian.study.http.urlconnection.HttpRequestor;

public class JavaHttpClient {

	private static final Logger logger = LoggerFactory.getLogger(JavaHttpClient.class);
	
	public static void httpRequest() throws Exception {
		
		/* Post Request */
		//Map<String, String> dataMap = new HashMap<String, String>();
		//dataMap.put("name", "zhangshan");
		//String html = new HttpRequestor().doPost("http://10.38.67.108:8080/SpringMVC/greeting", dataMap);
		
		/* Get Request */
		String html = new HttpRequestor().doGet("http://10.38.67.108:8080/SpringMVC/greeting?name=zhangshan");

		logger.info(html.replaceAll("\r\n", ""));
	}
}

MulThreadProcessClient.java

package com.bijian.study.client;

import com.bijian.study.http.ApacheClient;
import com.bijian.study.http.JavaHttpClient;

public class MulThreadProcessClient implements Runnable {
	
	public void run() {
		try {
			//Apache HttpClient
			//ApacheClient.httpRequest();
			
			//URLConnection HttpClient
			JavaHttpClient.httpRequest();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
        
		while(true) {
			MulThreadProcessClient r = new MulThreadProcessClient();  
	        Thread t = new Thread(r);
	        t.start();
	        try {
				Thread.sleep(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

        由于我是在Windows下进行开发的,因此采用wireshark抓包分析的,而非Linux下非常著名的tcpdump命令。


        如果是ApacheClient.httpRequest(),不管每发送一次请求休眠多少时间,每次请求都会有TCP三次握手(关于具体查看TCP三次握手请参看:http://bijian1013.iteye.com/blog/2299901),即都会重新建立连接,如下所示。


        而如果是JavaHttpClient.httpRequest(),休眠时间小于等于5秒,则没有TCP的三次握手,请求间隔大于5秒,则每次请求也有TCP的三次握手过程。如下所示:

        休眠时间小于等于5秒,无TCP的三次握手过程:


        休眠时间大于5秒,有TCP的三次握手过程:



二.分析

1.Apache HttpClient的调用逻辑分析

        1)HttpResponse response = httpclient.execute(httpget);

        2)org.apache.http.impl.client.CloseableHttpClient的execute方法

        3)org.apache.http.impl.client.InternalHttpClient的doExecute方法

        4)org.apache.http.impl.execchain.MainClientExec的execute方法


        5)org.apache.http.impl.execchain.MainClientExec的establishRoute方法

        6)org.apache.http.impl.conn.BasicHttpClientConnectionManager的connect方法

        7)org.apache.http.impl.conn.DefaultHttpClientConnectionOperator的connect方法


        从上面的源代码来看,每次连接都会新建Socket连接。

 

二.HttpURLConnection的调用逻辑分析

        1)com.bijian.study.http.urlconnection.HttpRequestor的doGet方法

        2)sun.net.www.protocol.http.HttpURLConnection的getInputStream方法


        2.1)sun.net.www.protocol.http.HttpURLConnection的connect方法

                2.1.1)sun.net.www.protocol.http.HttpURLConnection的plainConnect方法

                2.1.2)sun.net.www.protocol.http.HttpURLConnection的getNewHttpClient方法

                        2.1.2.1)sun.net.www.http.HttpClient的New方法

                        2.1.2.2)sun.net.NetworkClient的openServer方法

        2.2)sun.net.www.http.HttpClient的parseHTTP方法

                sun.net.www.http.HttpClient的parseHTTPHeader方法


        2.3)sun.net.www.http.HttpClient的finished方法

                2.3.1)sun.net.www.http.HttpClient的putInKeepAliveCache方法

                2.3.2)sun.net.www.http.KeepAliveCache的put方法


        也可以参看http://stackoverflow.com/questions/4767553/safe-use-of-httpurlconnection的如下内容。


        于是我修改我的测试代码,增加System.setProperty("http.keepAlive", "false"),如下所示:


        将休眠时间改成远小于5秒,测试发现每次请求也都会有TCP的三次握手过程,如下所示。

 

PS:

1.Wireshark的Filter内容:(http or tcp) and ip.src == 10.38.67.108 and ip.dst == 10.38.67.108 and tcp.port == 8080,这里我本机的IP地址是10.38.67.108。

2.请求的URL要写IP地址,不要写localhost,写localhost用Wireshark将抓不到包。如果要抓本地包,请查看wireshark如何抓取本机包

3.由于我把本机既作为客户端又作为服务器端来调试代码,使得本机自己和自己通信。但是,这样wireshark是无法抓取到数据包的,需要通过如下简单的设置才可以,具体方法如下(详细及另外的方法请参看:wireshark如何抓取本机包):

        ①:以管理员身份运行cmd

        ②:route add 本机ip mask 255.255.255.255 网关ip

        此时再利用wireshark进行抓包便可以抓到本机自己同自己的通信包。

4.Web Application应用的工程请直接到http://bijian1013.iteye.com/blog/2299764获取,测试验证工程请见附件TestHttpClient.zip。

猜你喜欢

转载自bijian1013.iteye.com/blog/2299764