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