背景
最近是个特殊的阶段,整理自己的知识tree。在网络协议这一块,涉及比较多会有tcp/ip传输层/网络层协议、UDP传输层协议、http/https应用层协议、mysql/redis/mc等应用协议。而在这些协议的连接中都会提到长连接、短连接的概念,需要将其解析清楚。借team内部小分享的机会,把这部分知识进行细化,也留下了之后思考的空间。
参考资料
正文
很明显的,目前经常使用的应用层协议是基于传输层TCP协议的上层协议,因此首先对TCP的长、短连接的定义要清晰(关于tcp/ip两个重要协议的细节内容,会单开一篇来讲)。
首先简单复习一下TCP协议中的连接/断开的三次握手/四次挥手,盗用经典图一张(如有侵权,辛苦通知删除):
这里有个非常有意思的讨论,为啥tcp非要是三次握手,具体的理论证明这里不展开,但是可以利用一个通俗的解释这部分流程blog:
首先了解tcp协议是一个在非可信信道下的双工协议,所以我们理解三次握手的逻辑为:
- 第一次:建立连接。客户端发送连接请求报文段,将标志位设置为SYN,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;(a -> b 你能听懂我说话么?)
- 第二次:服务器收到SYN报文段标志位,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将标志位设置SYN,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;(b -> a 我能听懂你,你呢,能听懂我吧?)
- 第三次:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。(a -> b 我也没问题啊,开聊吧~)
- 正常状态下两次应该就可以唯一确定一个通信信道,为何要再通信一次?谢仁希《计算机网络》中举了一个例子:
“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
四次挥手:
经过查阅资料,我们可以发现TCP短连接/长连接并没有明确的定义,长连接是相对于短连接而说的,顾名思义,也就是客户端与服务端长时间保持连接状态,简单下一下定义(暂时没有在wikipedia找到定义):
TCP短链接:建立一个tcp连接,数据发送完成后,断开此tcp。
优点明显,服务端管理简单(服务端编程友好),每个当前存在的连接都是有用连接,不需要做任何额外的控制。
TCP长连接:指的是在一个tcp连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方提供维持连接的机制(TCP/IP详解上详细描述了保活功能)。
TCP长连接保活功能:
保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。
如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:
- 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
- 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。(150*10 = 1500s)
- 客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
- 客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。
从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。
由于在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务(浏览器的设置)。
优缺点对比:
优劣 | 长连接 | 短连接 |
---|---|---|
连接数占用 | 少 | 多 |
切换系统开销 | 少 | 多 |
安全性 | 差 | 好 |
并发能力 | 低 | 强 |
简单描述一下具体的应用层协议对长短连接的定义:
http长短连接:
HTTP 1.0 开启长连接,需要增加request头:Connection:Keep-Alive
HTTP 1.1 默认连接保持,关闭需要增加request头:Connection: close
keep-alive connection:(客户端不会主动断开连接,连接时长完全取决于服务端,不同的服务组件(apache、nginx)提供不同的时长)
multiple connection:一次收发后,客户端主动断开连接。
http协议的长连接相关知识,使用nodejs代码测试:
nodejs 先看一下短链接,查看一下response
删掉destroy 我们就是长连接了,结果pending 因为浏览器不知道啥时候结束;
很简单的一个思路,我告诉浏览器长度(http协议),果然又可以展示了
然后我们看看baidu 没有length,但是有一个可疑的(HTTP Strict Transport Security是安全线管,告诉浏览器只能用https访问)
为什么捏? 因为当前的页面很复杂,通常都是动态语言生成,或者来源于网络文件,那么在程序中开一个足够大的buffer,那么对内存的占用基本是不可容忍的。同时用户的等待也过久了。实际的长度十分难以获得。我们关注的关键数据TTFB (Time To First Byte)也不能容忍等待整个页面数据长度的计算。
所以定位这个可疑的:Transfer-Encoding: chunked
所以回到BP的HTTP chunk:
这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\\r\\n),也不包括分块数据结尾的 CRLF。
redis的长、短连接:
客户端:目前客户端默认不会主动close,如果有这个需求,需要在config中设置timeout,则客户端会在timeout时间后主动断开;
服务端:redis3.2之后服务端增加了对tcp层keepalive的支持(默认打开设置为300s)此选项可以解决客户端未主动关闭导致的服务端维护过多无用connction的情况,但也会引入一个问题:当一个客户端连接不活跃时服务端会主动断开连接,导致在这个client上的操作返回一个“connection closed”的exception。
MySQL的长、短连接: