了解TCP传输控制协议
TCP的定义
TCP
即传输控制协议,全称为Transition Control Protocol
,工作在传输层上。主要职责是负责主机之间进程到进程的通信,其次可以保证可靠性,不能保证安全性。
- TCP会尽自己所能,尽量将数据发送给对方;但并不能保证100%可以发送给对方
- TCP会在数据发送不到对方的情况下,会给应用层一个错误提示,告知用户发送失败
- TCP可以保证接收方(应用层)严格按照发送时的数据顺序接收
- TCP保证数据不会出现无意间的损坏(UDP 也做到这点)
- TCP尽可能地维护网络质量
TCP的特性
1、确认应答机制(ACK):
确认应答机制是可靠传输的最核心机制:接收方反馈一个应答报文(ACK),表示已经收到。
但是可能出现一种情况,后发先至,即接收到的数据是乱序的。
如果存在对应关系,即使出现了这种情况,也可以顺利的确立应答。
虽然能够正确的建立应答,但是额外的信息太多,占用了很多带宽,可以进行编号,应答的时候针对编号进行应答,这样既能保证没有歧义,也能不浪费带宽。
序号和确认序号并不是按照一条两条来编的,而是按照字节来编的,每一个字节均有一个编号。此时的ACK本质上是一个字段为1的报文,既是确认序号,也是下次发送的序号。
为了防止网络攻击,初始的序号是随机的,例如上图,初始序号为66,发送了22字节,应该到87,则回复88,认为88之前的都已经收到了。
2、超时重传
确认应答是比较理想的情况,但是数据再传输过程中,可能是丢包的。上图为例,如果A给B发信息,并未收到回复,此时有三种情况。
- B不想回
- B没有收到——A发出的请求丢失
- B收到了,恢复了,A没收到——B应答的ACK丢失
因为丢失的时候,无法确定哪一种情况,因此统一处理,当发送数据之后,TCP内部就会启动一个定时器,达到一定时间没有收到ACK之后,定时器就会触发重传消息的操作——超时重传。
如果第二次重传依旧没有成功,就会存在两个超时时间,往往t2
会比 t1
更长
TCP 抱着一种 “悲观的态度”,当一次丢包重传之后,TCP 就觉得大概率后面的重传也没用,所以就隔一个更长的时间,节省带宽。
上述丢包有两种情况,一种是请求丢失——重传没有问题;一种是 ACK 丢失,重传就意味着接收方收到了相同的数据
TCP 会在内部进行数据去重 (以序号为 key 进行去重),保证应用层读到的数据不是重复数据。
3、建立连接 三次握手
TCP三次握手的过程——情景模拟
- 发送方:老铁,可以听得到我说话吗,老铁。
- 接收方:可以听到,你听得到吗?
- 发送方:听到了,那我开始说正事了。
这个故事是用来模拟TCP三次握手的,也是在所有通俗解释三次握手的典型例子。
为什么要建立连接?
- 为了保证双方能够正常通信,更好的保证可靠性,建立连接的过程其实就是验证双方各自的发送能力和接受能力是否正常。
- 协商一些参数、例如序号的初始值。
怎样建立连接?
第一次握手:刚开始A和B都不知道听筒和话筒是否正常,因此说一句话。
第二次握手:接收方接收到了信息,需要给发送方一个回复。
第三次握手:发送方再给接收方一个回复,说明都正常。
我理解的:
现在2个人 不知道 对方的听筒可以听到 话筒能不能使用保持疑问?
经过以上三次握手,证明网络建立连接时候是正常的
第一次握手:客户端向服务器发送报文SYN(SEQ=x,SYN=1)
,并进入SYN_SENT
状态,等待服务器确认。SEQ
是序列号
第二次握手包含两部分,一部分是请求,另一部分是应答,应答是ACK=x+1
,发送SYN SEQ=y
,此时服务器进入SYN_RECV
状态
第三次握手,客户端收到了ACK
,并对SYN
进行应答,完成之后进入Establishment
状态,完成三次握手。
建立连接的过程,相当于通信双方各自给对方发送 SYN,在各自给对方发送给 ACK,只不过中间的 ACK 和 SYN 合二为一了,于是最后就是"三次握手。
几个重要的状态:
- LISTEN: 正在侦听来自远方的 TCP 端口的连接请求,服务端启动后处于 LISTEN 状态用于监听不同客户端的 TCP 请求并建立连接
- SYN_SEND / SYN_RCVD: 建立连接的中间过程,若连接顺利的话(建立连接过程也可能丢包),这两个状态就一瞬消失
- ESTABLISHEN: 连接建立完毕 (验证了通信双方的发送和接受能力都正常),可以进行数据传输。
为什么不能两次握手:
两次握手只能保证单向连接是通畅的 (为了实现可靠数据传输, TCP 协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的;三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤;如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认)
4、四次挥手 双方各自向对方发起建立连接的请求,再各自给对方回应,只不过,中间的 FIN 和 ACK 不一定能合并在一起
- 第一次挥手: 客户端向服务器端发送断开 TCP 连接请求的 [FIN,ACK] 报文,在报文中随机生成一个序列号 SEQ=u,表示要断开 TCP 连接,此时,客户端进入FIN_WAIT_1 (终止等待1) 状态
- 第二次挥手:当服务器端收到客户端发来的断开 TCP 连接的请求后,回复发送 ACK 报文,表示已经收到断开请求。回复时,随机生成一个序列号 SEQ=v;由于回复的是客户端发来的请求,所以在客户端请求序列号 SEQ=u 的基础上加 1,得到 ack=u+1此时,服务端就进入了CLOSE_WAIT (关闭等待) 状态,客户端收到ACK后,就进入FIN_WAIT_2 (终止等待2) 状态
- 第三次挥手:服务器端在回复完客户端的 TCP 断开请求后,不会马上进行 TCP 连接的断开。服务器端会先确认断开前,所有传输到客户端的数据是否已经传输完毕**。确认数据传输完毕后才进行断开,向客户端发送 [FIN,ACK] 报文,设置字段值为 1**。再次随机生成一个序列号 SEQ=w;由于还是对客户端发来的 TCP 断开请求序列号 SEQ=u 进行回复,因此 ack 依然为 u+1,此时,服务器就进入了LAST_ACK (最后确认) 状态
- 第四次挥手:**客户端收到服务器发来的 TCP 断开连接数据包后将进行回复,表示收到断开 TCP 连接数据包。**向服务器发送 ACK 报文,生成一个序列号 SEQ=u+1;由于回复的是服务器,所以 ACK 字段的值在服务器发来断开 TCP 连接请求序列号 SEQ=w 的基础上加 1,得到 ack=w+1,此时,客户端就进入了TIME_WAIT (时间等待) 状态;注意此时TCP连接还没有释放,必须经过2MSL (最长报文段寿命) 的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
另一篇博客的说法
在断开连接之前客户端和服务器都处于ESTABLISHED状态,双方都可以主动断开连接,以客户端主动断开连接为优。
第一次挥手:客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。
也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。
第二次挥手:服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。
接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。
第三次握手:服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。
服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。
第四次握手:客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。
客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。
由客户端到服务器需要一个FIN和ACK,再由服务器到客户端需要一个FIN和ACK,因此通常被称为四次握手。
客户端和服务器都可以主动关闭连接,只有率先请求关闭的一方才会进入TIME_WAIT(时间等待状态)。
为什么挥手需要四次?
这是由于TCP的半关闭(half-close)造成的。半关闭是指:TCP提供了连接的一方在结束它的发送后还能接受来自另一端数据的能力。通俗来说,就是不能发送数据,但是还可以接受数据。
TCP不允许连接处于半打开状态时,就单向传输数据,因此完成三次握手后才可以传输数据(第三握手可以携带数据)。
当连接处于半关闭状态时,TCP是允许单向传输数据的**,也就是说服务器此时仍然可以向客户端发送数据,等服务器不再发送数据时,才会发送FIN报文段,同意现在关闭连接。**
这一特性是由于TCP双向通道互相独立所导致的,也使得关闭连接必须经过四次握手。
可能有些人会有疑惑:为什么中间的ACK和FIN不可以像三次握手那样合为一个报文段呢?
在socket网络编程中,执行close()方法会触发内核发送FIN报文。什么时候调用close()方法,这是由用户态决定的,假如服务器仍有大量数据等待处理,那么服务器会等数据处理完后,才调用close()方法,这个时间可能会很久,而ACK报文则是由系统内核来完成的,这个过程会很快。所以中间的ACK和FIN不能合为一个包。
为什么TIME_WAIT等待的时间是2MSL?
MSL(Maximum Segment LifeTime)是报文最大生成时间,它是任何报文在网络上存在的最长时间,超过这个时间的报文将被丢弃。
因为TCP协议是基于IP协议(位于IP协议的上一层),IP数据报中有限制其生存时间的TTL字段,是IP数据报可以经过的最大路由器的个数,每经过处理它的路由,TTL就会减一。TTL为 0 时还没有到达目的地的数据报将会被丢弃,同时发送 ICMP 报文通知源主机。
MSL的单位为时间,TTL的单位为跳转数。所以MSL应该大于等于TTL变为0的时间,以确保报文已被丢弃。
TIME_WAIT等待的2MSL时间,可以理解为数据报一来一回所需要的最大时间。
2MSL时间是从客户端接收到FIN后发送ACK开始计时的。如果在这个时间段内,服务器没有收到ACK应答报文段,会重发FIN报文段,如果客户端收到了FIN报文段,那么2MSL的时间将会被重置。如果在2MSL时间段内,没有收到任何数据报,客户端则会进入CLOSE状态。
等待2MSL的意义
- 保证客户端最后发送的ACK能够到达服务器,帮助其正常关闭。
由于这个ACK报文段可能会丢失,使得处于LAST_ACK状态的服务器得不到对已发送FIN报文段的确认,从而会触发超时重传。服务器会重发FIN报文段,客户端能保证在2MSL时间内收到来自服务器的重传FIN报文段,从而客户端重新发送ACK应答报文段,并重置2MSL计数。
假如客户端不等待2MSL就之间进入CLOSE状态,那么服务器会一直处于LAST_ACK状态。
当客户端发起建立SYN报文段请求建立新的连接时,服务端会发送RST报文段给客户端,连接建立的过程就会被终止。
- 防止已失效的连接请求报文段出现在本连接中。
TIME_WAIT等待的2MSL时间,确保本连接内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
TIME_WAIT状态过多有什么危害?
只有主动发起断开请求的一方才会进入TIME_WAIT状态!
- 占用系统资源
- socket的TIME_WAIT状态结束之前,该socket占用的端口号将一直无法释放。如果服务器TIME_WAIT状态过多,占满了所有端口资源,则会导致无法创建新的连接。