一般情况下,我们了解到http连接 创建时涉及3次握手,如果每次连接都只用于一次http请求,效率就很低,所以后来有了长连接的概念
先复习一下短连接的过程
参考文章:
《TCP三次握手 四次挥手全过程》
理解TCP序列号(Sequence Number)和确认号(Acknowledgment Number)
0.准备知识
TCP标志位
TCP在其协议头中使用大量的标志位或者说1位(bit)布尔域来控制连接状态,一个包中有可以设置多个标志位。
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:
位码即TCP标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)
我们常用的是以下三个标志位:
SYN - 创建一个连接
FIN - 终结一个连接
ACK - 确认接收到的数据
其中:TCP会话的每一端都包含一个32位(bit)的序列号,该序列号被用来跟踪该端发送的数据量。每一个包中都包含序列号,在接收端则通过确认号用来通知发送端数据成功接收
1.tcp/ip 的连接创建
IP 192.168.1.116.3337 > 192.168.1.123.7788: S 3626544836:3626544836
IP 192.168.1.123.7788 > 192.168.1.116.3337: S 1739326486:1739326486 ack 3626544837
IP 192.168.1.116.3337 > 192.168.1.123.7788: ack 1739326487,ack 1
第一次握手:192.168.1.116发送位码syn=1,随机产生seq number=3626544836的数据包到192.168.1.123,192.168.1.123由SYN=1知道192.168.1.116要求建立联机;
第二次握手:192.168.1.123收到请求后要确认联机信息,向192.168.1.116发送ack number=3626544837,syn=1,ack=1,随机产生seq=1739326486的包;
第三次握手:192.168.1.116收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,192.168.1.116会再发送ack number=1739326487,ack=1,192.168.1.123收到后确认seq=seq+1,ack=1则连接建立成功。
2.tcp/ip 连接的关闭
由于tcp是全双工通信,两端都可以发送信息。所以主动进行关闭的一方进行主动关闭,而另一方执行被动关闭。主动关闭时,可能另一方还有数据要传输等,所以要收到FIN信号后,仍可以发送数据
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1
tip: 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
3.回到长连接
http 1.0默认连接为短连接,如要使用长连接,需要再头部 添加Connection:Keep-Alive来实现长连接
而http1.1则默认为长连接,不想使用时,在头部显示声明 Connection:close
所以只要web容器支持长连接,那么关系到是否用长连接其实是发送请求者的http请求头决定的
如Tomcat如何使用长连接,就是SocketProcessor 任务包含N次请求相应周期的循环处理,循环步骤为首先读取客户端http请求报文,解析报文,处理逻辑,响应客户端,然后结束一个请求周期,接着又是这么一个循环。知道出现某些情况才能关闭连接,如最大复用请求数,超时或者异常等
关于长连接的BIO和NIO
BIO受限于本身的模式,连接数和线程数是1:1,连接数上升会导致线程数上升,导致JVM线程过多切换成本高
而NIO可以克服不足把事件的遍历交给少量线程,再把具体事情的执行交给线程池