JDK对Http协议的Keep-Alive的支持,以JDK8为例
JDK對Http協議的Keep-Alive的支持,以JDK8為例
Http協議對keep-alive的支持
? keep-alive顧名思義就是保持連接的意思,在早期的HTTP/1.0中,每次http請求都要創建一個連接,而創建連接的過程需要消耗資源和時間,為了減少資源消耗,縮短響應時間,就需要重用連接。
? 在后來的HTTP/1.0中以及HTTP/1.1中,引入了重用連接的機制,就是在http請求頭中加入Connection: keep-alive來告訴對方這個請求響應完成后不要關閉,下一次咱們還用這個請求繼續交流。
? 協議規定HTTP/1.0如果想要保持長連接,需要在請求頭中加上Connection: keep-alive,而HTTP/1.1默認是支持長連接的,有沒有這個請求頭都行。另外,一般服務端都會設置keep-alive超時時間。超過指定的時間間隔,服務端就會主動關閉連接。同時服務端還會設置一個參數叫最大請求數,比如當最大請求數是300時,只要請求次數超過300次,即使還沒到超時時間,服務端也會主動關閉連接。如下圖所示為服務器返回的Responser Headers的值。
? 注意,當Connection:keep-alive存在時,下面的Keep-Alive: timeout=70, max=10才會生效。
? Http協議對keep-alive的支持是基于TCP連接的成功建立,而TCP協議是對Http透明的,即TCP協議的Keep-Alive與Http的Keep-Alive是無關的。
TCP協議中的keep-Alive
? TCP keepalive指的是TCP保活計時器(keepalive timer)。設想有這樣的情況:客戶已主動與服務器建立了TCP連接。但后來客戶端的主機突然出故障。顯然,服務器以后就不能再收到客戶發來的數據。因此,應當有措施使服務器不要再白白等待下去。這就是使用保活計時器。服務器每收到一次客戶的數據,就重新設置保活計時器,時間的設置通常是兩小時。若兩小時沒有收到客戶的數據,服務器就發送一個探測報文段,以后則每隔75秒發送一次。若一連發送10個探測報文段后仍無客戶的響應,服務器就認為客戶端出了故障,接著就關閉這個連接。
——摘自謝希仁《計算機網絡》
JDK對Http協議的keep-alive的支持
? JDK對Http協議的Keep-Alive的支持參考oracel官方說明https://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html。接下來結合jdk源碼分析:
1、HttpURLConnection(java.net.HttpURLConnection)使用長連接
? JDK自帶的HttpURLConnection,默認啟用keepAlive,支持HTTP / 1.1和HTTP / 1.0持久連接, 使用后的HttpURLConnection會放入緩存中供以后的同host:port的請求重用,底層的socket在keepAlive超時之前不會關閉。
? HttpURLConnection受以下system properties控制:
? http.keepAlive=(默認值:true),是否啟用keepAlive,如果設置為false,則HttpURLConnection不會緩存,使用完后會關閉socket連接。(可設置)
? http.maxConnections=(默認值:5),每個目標host緩存socket連接的最大數。(當http.keepAlive=false時為1,否則為5,下面結合源碼分析)
2、sun.net.www.http包下的HttpURLConnection和HttpClient長連接緩存
? sun.net.www.http.HttpURLConnection是java.net.HttpURLConnection的實現類,JDK自帶的HttpURLConnection底層使用JDK自帶的HttpClient發送http請求,java8的HttpClient(sun.net.www.http.HttpClient)存在諸多限制,在Jdk11中,java.net.HttpClient吸收第三方HttpClient的優點,性能肩比第三方HttpClient(okHttptClitn,apache Httpclient)。
? 下面為sun.net.www.http.HttpURLConnection.HttpURLConnection類,有如下兩個方法返回HttpClient的實例和直接關閉socket的disconnect()方法。
public class HttpURLConnection extends java.net.HttpURLConnection {// subclass HttpsClient will overwrite & return an instance of HttpsClientprotected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout)throws IOException {return HttpClient.New(url, p, connectTimeout, this);}// subclass HttpsClient will overwrite & return an instance of HttpsClientprotected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout, boolean useCache)throws IOException {return HttpClient.New(url, p, connectTimeout, useCache, this);}/*** Disconnect from the server (public API)*/public void disconnect() {} }? 下面為HttpClient類,parseHTTPHeader會解析header參數,判斷HttpURLConnection是否是長連接,如果是長連接會讀取keepAliveTimeout參數作為KeepAliveCache類里的長連接的緩存有效時間(默認為0)。并設置keepAliveConnections的值,如過為長連接值為5,否則為1。
? 完成獲取請求返回結果后或調用getInputStream().close(),JDK會清理連接并作為以后使用的連接緩存。具體邏輯為執行finished()方法,如果是短連接,直接關閉套接字(調用closeServer()方法),如果是長連接,則將這個長連接加到KeepAliveCache的緩存線程中(putInKeepAliveCache()方法)。
? 其中,protected static KeepAliveCache kac = new KeepAliveCache();代碼表面運行時只有全局只有一個緩存的線程類。
public class HttpClient extends NetworkClient {// whether this httpclient comes from the cacheprotected boolean cachedHttpClient = false;protected boolean inCache;/* where we cache currently open, persistent connections */protected static KeepAliveCache kac = new KeepAliveCache();private static boolean keepAliveProp = true;private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc){keepAliveConnections = -1;keepAliveTimeout = 0;}public void finished() {if (reuse) /* will be reused */return;keepAliveConnections--;poster = null;if (keepAliveConnections > 0 && isKeepingAlive() &&!(serverOutput.checkError())) {/* This connection is keepingAlive && still valid.* Return it to the cache.*/putInKeepAliveCache();} else {closeServer();}}protected synchronized void putInKeepAliveCache() {if (inCache) {assert false : "Duplicate put to keep alive cache";return;}inCache = true;kac.put(url, null, this);} }? KeepAliveCache繼承了HashMap,實現了Runnable接口。
? 它本身可以存儲不同的KeepAliveKey-ClientVector,KeepAliveKey是關于協議(一般為http),ip,port類,對應了一個socket連接。ClientVector實現了雙端隊列,存儲了HttpClient實例和idleStartTime(該HttpClient開始空閑的時間),最多可以存5個,對應HttpClient類的keepAliveConnections。ClientVector還有一個屬性nap,即緩存過期時間,在put()方法實例化時設置,new ClientVector(keepAliveTimeout > 0 ? keepAliveTimeout * 1000 : LIFETIME)
? KeepAliveCache也是一個線程類,run方法的邏輯是do-while循環檢測HashMap中緩存的長連接是否timeout,如果超時就清理,當HashMap為空時,線程完成自己的邏輯,執行完畢。
? KeepAliveCache提供put()方法,運行時KeepAliveCache線程類全局只有一個,沒有緩存的線程類時,在put()方法中創建該緩存類keepAliveTimer,并設置長連接的緩存時間。
public class KeepAliveCacheextends HashMap<KeepAliveKey, ClientVector>implements Runnable {/* Sleeps for an alloted timeout, then checks for timed out connections.* Errs on the side of caution (leave connections idle for a relatively* short time).*/@Overridepublic void run() {do {try {Thread.sleep(LIFETIME);} catch (InterruptedException e) {}// Remove all outdated HttpClients.synchronized (this) {long currentTime = System.currentTimeMillis();List<KeepAliveKey> keysToRemove = new ArrayList<>();for (KeepAliveKey key : keySet()) {ClientVector v = get(key);synchronized (v) {KeepAliveEntry e = v.peek();while (e != null) {if ((currentTime - e.idleStartTime) > v.nap) {v.poll();e.hc.closeServer();} else {break;}e = v.peek();}if (v.isEmpty()) {keysToRemove.add(key);}}}for (KeepAliveKey key : keysToRemove) {removeVector(key);}}} while (!isEmpty());}/*** Register this URL and HttpClient (that supports keep-alive) with the cache* @param url The URL contains info about the host and port* @param http The HttpClient to be cached*/public synchronized void put(final URL url, Object obj, HttpClient http) {boolean startThread = (keepAliveTimer == null);if (!startThread) {if (!keepAliveTimer.isAlive()) {startThread = true;}}if (startThread) {clear();/* Unfortunately, we can't always believe the keep-alive timeout we got* back from the server. If I'm connected through a Netscape proxy* to a server that sent me a keep-alive* time of 15 sec, the proxy unilaterally terminates my connection* The robustness to get around this is in HttpClient.parseHTTP()*/final KeepAliveCache cache = this;AccessController.doPrivileged(new PrivilegedAction<>() {public Void run() {keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);keepAliveTimer.setDaemon(true);keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);keepAliveTimer.start();return null;}});}KeepAliveKey key = new KeepAliveKey(url, obj);ClientVector v = super.get(key);if (v == null) {int keepAliveTimeout = http.getKeepAliveTimeout();v = new ClientVector(keepAliveTimeout > 0 ?keepAliveTimeout * 1000 : LIFETIME);v.put(http);super.put(key, v);} else {v.put(http);}}class ClientVector extends ArrayDeque<KeepAliveEntry> {// sleep time in milliseconds, before cache clearint nap;ClientVector(int nap) {this.nap = nap;}}class KeepAliveKey {private String protocol = null;private String host = null;private int port = 0;private Object obj = null; // additional key, such as socketfactory/*** Constructor* @param url the URL containing the protocol, host and port information*/public KeepAliveKey(URL url, Object obj) {this.protocol = url.getProtocol();this.host = url.getHost();this.port = url.getPort();this.obj = obj;}}class KeepAliveEntry {HttpClient hc;long idleStartTime;KeepAliveEntry(HttpClient hc, long idleStartTime) {this.hc = hc;this.idleStartTime = idleStartTime;}} }3、HttpURLConnection使用短連接
3.1)使用短連接,不緩存長連接
? 設置System.setProperty(“http.keepAlive”, ”false”);將整個 APP 的 http 長連接支持關掉。不會啟動新的線程去緩存HttpClient連接,在HttpClient類的finished方法里不執行緩存連接的操作,直接關閉socket連接。
3.2)客戶端不使用keep-alive功能
? HttpURLConnection的實例獲取到服務方的數據后直接關閉HttClient(關閉socket),不緩存如下所示:
//第一種,Header指定短連接 httpConn.setRequestProperty("Connection", "close"); //第二種,請求完后直接關閉socket httpURLConnection.disconnect();3.3)服務端長連接關閉
? 返回的Response Header中包含Connection:close即可。
參考另外幾位大佬們的文章:
http://www.itersblog.com/archives/3.html
https://blog.csdn.net/u012216753/article/details/78084327
https://blog.csdn.net/tianshouzhi/article/details/103922842?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-5&spm=1001.2101.3001.4242
總結
以上是生活随笔為你收集整理的JDK对Http协议的Keep-Alive的支持,以JDK8为例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP中汇率固定配置和应用分析测试
- 下一篇: 〖Python WEB 自动化测试实战篇