欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

JDK对Http协议的Keep-Alive的支持,以JDK8为例

发布时间:2024/3/13 编程问答 70 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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的值。

Reponse HeadersConnection: Keep-AliveContent-length: 35content-type: application/json;charset=UTF-8Keep-Alive: timeout=60,max=5

​ 注意,当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为例的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。