由于前段时间对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方法
二.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。