做web开发的老铁应该都知道http协议,它是前后端通信中非常常用的一种通信协议,HTTP(HyperText Transfer Protocol)即超文本传输协议,是互联网上应用最为广泛的一种网络协议。
HTTP协议是一个基于请求-响应模型的协议,客户端(浏览器、移动客户端等)发起请求,服务端接收请求后进行处理并返回响应。
在HTTP协议中,有两种连接方式:长连接和短连接,它们主要区别在于连接的持续时间和资源利用率。
长/短 连接到底是啥
长连接,又称持久连接(Persistent Connection),是指在一个TCP连接上可以连续发送多个请求和响应报文,在通信双方的任何一方主动关闭连接之前,连接一直保持。长连接在HTTP/1.1中是默认启用的,通过设置请求头部的Connection字段为Keep-Alive来实现,
短连接,又称非持久连接(Non-Persistent Connection),是指客户端与服务器每进行一次HTTP操作,就建立一次连接,任务结束后则断开连接。在HTTP/1.0中,短连接是默认的连接方式。
该用哪个
虽然我们知道了长连接和短连接是什么了,但是在实际的使用场景中,该如何选择呢?
下面我们来对比一下,长连接和短连接各自的使用场景。
使用长连接
可以减少了TCP连接的建立和关闭次数,降低了网络资源消耗。例如,对于一个网站,用户在浏览多个页面时,使用长连接可以避免频繁建立和关闭连接,节省了网络资源。
同时可以提高数据传输效率,减少了网络延迟。例如,在一个文件传输过程中,使用长连接可以避免每次传输一个文件都需要重新建立连接,从而提高了文件传输的效率。
当然长连接也有一些不足之处:
长连接会占用更多的服务器资源,如内存、文件描述符等。例如,当有大量客户端同时连接到服务器时,服务器需要为每个客户端保持一个连接,可能导致服务器资源耗尽。
当客户端数量较多时,可能导致服务器资源耗尽。
使用短连接
短连接相对长连接,不会长时间的占用服务器资源。适用于请求量较小、连接数较多的场景。例如,在一个物联网应用中,大量设备定时向服务器发送数据,短连接可以避免长时间占用服务器资源。
短连接的不足之处也很明显:
短连接需要频繁建立和关闭TCP连接,增加了网络资源消耗。例如,在一个网站中,用户访问多个页面时,使用短连接需要为每个页面重新建立连接,消耗了较多的网络资源。
如何管理长连接
上文说了,长连接和短连接的使用场景和不足之处,那么我们具体该怎么使用呢?
其实在平时的业务开发的过程中,我们经常使用的方案是这样的:
如果并发量不大的系统中可以使用长连接,同时设置长连接的超时过期时间,
超时过期:如果一个链接上长时间没有请求的话,那么就断开这个链接, 比如,nginx 提供的 keepalive_timeout 参数
如果是并发量比较大的系统,那么就要使用长连接了,因为在大并发的情况下,使用短连接需要频繁建立和断开连接,产生很多不必要的消耗,除此之外,链接断开的过程中会在 time_wait 状态下停留2MSL的时间间隔,这段时间链接使用的四元组会被占用,这个对客户端和服务端都有一定的影响,但是这种状态对服务端和客户端的影响是不一样的,有老铁知道其中的差异吗?
而且这里还要注意一点,很多服务端在实现长连接的过程中会控制单个长连接上请求的个数,也是说,一个链接上请求个数超过一定数量后,服务端就会强制断开这个连接,比如 nginx 的 keepalive_requests 参数
如果并发量比较大,同时这限制的阈值比较小的话,还是会存在大量的 time_wait状态的链接。
实操一下
下面我们使用java语言中的技术栈,通过一个简单的例子来模拟一下,长连接和短连接。
短连接实现
客户端
public static void main(String[] args) {HttpClient client = HttpClients.createDefault();for (int i = 0; i < 10; i++) {InputStream in = null;HttpGet httpGet = new HttpGet("http://127.0.0.1:8086/req");Header[] headerArrays = new Header[]{new BasicHeader("Connection", "close")};httpGet.setHeaders(headerArrays);try {HttpResponse response = client.execute(httpGet);System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));in = response.getEntity().getContent();} catch (Exception e) {e.printStackTrace();} finally {IOUtils.closeQuietly(in);}}}
服务端
@GetMapping(value = "/req")public String req(HttpServletRequest request){String remoteHost = request.getRemoteHost(); // client ipint remotePort = request.getRemotePort(); // client portSystem.out.println( remoteHost + ":" + remotePort);return "req";}
在上述代码中
服务端使用spring-boot中自带的tomcat作为http服务器,处理请求的逻辑也比较简单,当收到客户端的请求后,打印出客户端发送请求时,创建链接使用的ip和端口号。
客户端使用http-client作为http的客户端。
实现短连接的关键在于客户端发送请求使用如下的http请求头:
"Connection", "close"
该请求头的含义表示,告诉服务端:我这次发送的http请求使用的是短连接,你处理完请求后,主动断开连接。
通过查看服务端的日志可以发现,每次请求使用的端口号,都是不同的,表示每次发起请求都是新建的链接。
长连接实现
客户端
public static void main(String[] args) {HttpClient client = HttpClients.createDefault();for (int i = 0; i < 10; i++) {InputStream in = null;HttpGet httpGet = new HttpGet("http://127.0.0.1:8086/req");Header[] headerArrays = new Header[]{new BasicHeader("Connection", "Keep-Alive")};httpGet.setHeaders(headerArrays);try {HttpResponse response = client.execute(httpGet);System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));in = response.getEntity().getContent();} catch (Exception e) {e.printStackTrace();} finally {IOUtils.closeQuietly(in);}}}
服务端
@GetMapping(value = "/req")public String req(HttpServletRequest request){String remoteHost = request.getRemoteHost(); // client ipint remotePort = request.getRemotePort(); // client portSystem.out.println( remoteHost + ":" + remotePort);return "req";}
上面实现长连接的代码和实现短连接时基本相同,差异的地方在于客户端发送请求使用的请求头做了调整:
"Connection", "Keep-Alive"
再次观察服务端打印的日志,输出的客户端端口号都是一样的:
管理长连接
上文说了,长连接长时间保持着,会占用系统资源,所以tomcat等众多http服务器提供了一些参数,来控制长连接的“长度”,
1.长连接有效时间:
如果一个链接上一定时间内没有请求的话,那么服务端就会主动断开连接。
可以在服务端添加tomcat相关的配置:
server:port: 8086servlet:context-path: /tomcat:keep-alive-timeout: 2s
上述配置 keep-alive-timeout 来配置长连接的空闲超时时间。
我们可以修改客户端程序进行验证,修改后的客户端程序如下:
public static void main(String[] args) {HttpClient client = HttpClients.createDefault();for (int i = 0; i < 10; i++) {InputStream in = null;HttpGet httpGet = new HttpGet("http://127.0.0.1:8086/req");Header[] headerArrays = new Header[]{new BasicHeader("Connection", "Keep-Alive")};httpGet.setHeaders(headerArrays);try {HttpResponse response = client.execute(httpGet);System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8"));in = response.getEntity().getContent();Thread.sleep(3000); // 等待3s} catch (Exception e) {e.printStackTrace();} finally {IOUtils.closeQuietly(in);}}}
客户端程序每隔3s发送一次请求,间隔时间超过长连接空闲超时时间2s。
重新启动客户端和服务端程序,服务端程序打印日志如下:
2.长连接上请求个数限制:
如果长连接上通过请求超过一定的数量,那么服务端也会主动断开连接。可以在服务端添加tomcat相关的配置:
server:port: 8086servlet:context-path: /tomcat:max-keep-alive-requests: 2
上述配置 max-keep-alive-requests 来配置长连接最大请求个数,这里配置为2,表示一个长连接上最多可以通过2个请求。
重新启动服务端和客户端程序,服务端程序打印日志如下:
从日志可以看出,只有连续两个请求的链接端口号是相同的。