一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:连接、数据传输、退出(关闭)。通过三次握手建立一个链接,通过四次挥手来关闭一个连接。通过多次握手和挥手,并根据对方上一条报文进行回应,代表自己确认收到上条信息。
TCP报文的头部结构
在了解TCP连接之前先来了解一下TCP报文的头部结构。
上图中有几个字段需要重点介绍下:
(1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
- ACK:确认序号有效。
- FIN:释放一个连接。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。
- URG:紧急指针(urgent pointer)有效。
需要注意的是:
- 确认方ack=发起方seq+1,两端配对。
- seq = 自己上次发的seq + 1,表示这是上次消息的下一条消息
三次握手
三次握手目的是开启一个连接
第一次握手:客户端向服务端发起连接要求,(标志位代表本次信息要做的事),发送
- SYN=1(标志位发起连接),seq = x(随机生成,如果收到请回应一个ack)
- 客户端发出了第一次握手,但不知道服务端是否连接,能否收到,自己的发消息能力是否有问题
- 确认事实:服务端确认了客户端的连接请求和发件能力和服务端的收件能力OK
第二次握手:服务端收到客户端的发起连接请求,并且表示自己可以连接和收到客户端的消息,发送
- SYN = 1(可以连接),ACK = 1(已收到你上条信息),seq = y(随机生成),ack = x + 1(确实收到你的消息,而且你上次消息seq是x,自证清白)
- 服务端发起了第二次握手,表示可以连接,可以回应,表示客户端的发消息能力没有问题,但不知道自己的发件能力是否有问题,以及本次的连接是否还作数(收到的第一次握手可能是网络延迟导致的过时的请求)
- 确认事实:客户端确认了客户端的发件能力和收件能力与服务端的发件能力和收件能力OK,服务端不清楚自己的发件能力和客户端的收件能力。
第三次握手:客户端收到服务端的可以连接消息,表示客户端收到以及服务端的发送能力没问题,以及这次连接作数,发送
- ACK = 1(已收到你上条消息),seq = x + 1(上次我发的是x,这次我发x+1,说明我是承认第一次的连接请求的), ack = y + 1(确实收到你的消息,你上次消息seq是y)
- 确认事实:客户端收发能力和服务端收发能力都OK,这次连接有效并开始
常见问题:
为什么不是两次握手?
为了防止已经失效的连接请求报文段突然又传送到了B,因而产生错误。
比如下面这种情况:A 发出的第一个连接请求报文段并没有丢失,而是在网络节点长时间滞留了,以致于延误到连接释放以后的某个时间段才到达 B。本来这是一个早已失效的报文段。但是 B 收到此失效的链接请求报文段后,就误认为 A 又发出一次新的连接请求。于是就向 A 发出确认报文段,同意建立连接。
对于上面这种情况,如果不进行第三次握手,B 发出确认后就认为新的运输连接已经建立了,并一直等待 A 发来数据。B 的许多资源就这样白白浪费了。
如果采用了三次握手,由于 A 实际上并没有发出建立连接请求,所以不会理睬 B 的确认,也不会向 B 发送数据。B 由于收不到确认,就知道 A 并没有要求建立连接。
为什么不是四次握手或者更多?
完全可靠的通信协议是不存在的。在经过三次握手之后,客户端和服务端已经可以确认之前的通信状况,都收到了确认信息。所以即便再增加握手次数也不能保证后面的通信完全可靠,所以是没有必要的。
第三次握手失败了怎么办?(服务端没有收到客户端的第三次握手报文?)
当失败时服务器并不会重传ACK报文段,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN泛洪攻击。
什么是SYN泛洪攻击?
TCP SYN泛洪发生在OSI第四层,这种方式利用TCP协议的特性,就是三次握手。攻击者发送TCP SYN,SYN是TCP三次握手中的第一个数据包,而当服务器返回ACK后,该攻击者就不对其进行再确认,那这个TCP连接就处于挂起状态挂起状态,也就是所谓的半连接状态,服务器收不到再确认的话,还会重复发送ACK给攻击者。这样更加会浪费服务器的资源。攻击者就对服务器发送非常大量的这种TCP连接,由于每一个都没法完成三次握手,所以在服务器上,这些TCP连接会因为挂起状态而消耗CPU和内存,最后服务器可能死机,就无法为正常用户提供服务了。
防范措施 对于SYN泛洪攻击的防范,优化主机系统设置是常用的手段。如降低SYN timeout时间,使得主机尽快释放半连接的占用;又比如采用SYN cookie设置,如果短时间内连续收到某个IP的重复SYN请求,则认为受到了该IP的攻击,丢弃来自该IP的后续请求报文。
Server 端收到 Client 端的 SYN 后,为什么还要传回 SYN?
接收端传回发送端所发送的SYN是为了告诉发送端,我接受到的信息确实就是你所发送的连接信号了,而不是其他信号。
SYN 是 TCP / IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误])消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
为什么要回传 SYN? 为什么还要传 ACK?
双方通信无误必须是两者互相发送信息都无误。传了SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要ACK信号来进行验证,再用ack表示自己确认的是哪一条消息。
四次挥手
四次挥手的目的是关闭一个TCP连接
第一次挥手:客户端向服务端发起断开连接请求,此时客户端需要发的全部发送完毕,(不发但可以接收)想要断开连接,发送:
- FIN = 1(断开连接请求,告诉对方我发完了要断开了),seq = u (随机,如果收到这条消息请回复 u +1)
- 客户端发送完毕,请求断开
第二次挥手:服务端收到客户端的断开请求,但此时服务端还有数据未发送完,发消息表示自己已收到,但还不能关闭连接,发送:
- ACK = 1(收到上条消息),seq = v(随机),ack = u+1(表示是对上条消息的回复确认)
- 服务端确认收到客户端断开请求但自己还有数据没发完
第三次挥手:服务端把最后的数据发完了,向客户端发起正式断开连接请求,发送:
- FIN = 1(我也想断开了),ACK = 1(再次确认收到第一次回收的消息),seq = w(随机,收到请回复w+1),ack = u +1(回应的是第一次挥手的断开请求,不是其他的断开请求)
- 服务端发送完毕,正式回应客户端的断开请求
第四次挥手:客户端接收到服务端的断开正式请求,发消息让双方都断开连接,发送
- ACK = 1(这是一条确认消息), seq = u + 1(表示是上一条消息的下一条),ack = w + 1(回应正式断开请求)
- 客户端收到了服务端正式断开请求,并表示自己已经收到
注意:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位、确认号ack、序列号seq注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
常见问题:
为什么TCP连接的时候是3次,关闭的时候却是4次,能不能合并?
因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。
-
为了保证 A 发送的最后一个 ACK 报文段能够到达 B。这个 ACK 报文段有可能丢失,因而使处在 LAST-ACK 状态的 B 收不到对已发送的 FIN + ACK 报文段的确认。B 会超时重传这个 FIN+ACK 报文段,而 A 就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个重传的 FIN+ACK 报文段。接着 A 重传一次确认,重新启动 2MSL 计时器。最后,A 和 B 都正常进入到 CLOSED 状态。如果 A 在 TIME-WAIT 状态不等待一段时间,而是在发送完 ACK 报文段后立即释放连接,那么就无法收到 B 重传的 FIN + ACK 报文段,因而也不会再发送一次确认报文段,这样,B 就无法按照正常步骤进入 CLOSED 状态。 -
防止已失效的连接请求报文段出现在本连接中。A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
|