Tcp:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
TCP 协议(包含三次握手,四次挥手)
TCP 特性
一、.确认应答机制 (ACK)
二、超时重传
三、建立连接 - 三次握手 ▲
四、断开连接 - 四次挥手 ▲
1.确认应答机制 (ACK)
确认应答是可靠传输的最核心机制
接收方反馈一个应答报文(ACK),表示已收到
假设现在 A 想去 B 家里玩游戏,于是 A 给 B 发消息,若消息没有出现错误且顺序正确
结果如下所示:
但网络传输比较复杂,可能存在一种情况"后发先至"
由于数据的长度不同或者传输网络不同,先发送的数据不一定先到达,接收方接收到的数据可能是乱序的,如图:
当 B 回复 A 的消息时,若存在对应关系,那么即使出现了"后发先至"的情况,也能顺利的确立应答
上述方法,虽然可以顺利的确立应答,但额外的信息很多,占用的带宽很多
下面如图,针对发送的请求进行编号,应答的时候也针对编号进行应答,这样既能保证数据传输没有歧义,也不会浪费太多的空间和带宽
序号和确定序号,在前面 TCP报文格式中提到过上述情况不严谨,真实的 TCP 还不一样,TCP 是面向字节流的,此处的编号并不是按照一条两条来编的,而是按照字节来编号的 (每个字节有一个编号)
确认应答是一种特殊的报文(ACK),所谓的应答报文,本质上就是 ACK 字段为1 的报文,此时报头中的"确认序号"字段才是生效的
初始序号是随机的,为了防止网络攻击;如果发送多个数据,每个数据都会带着一个序号
接收方收到数据后,是知道数据所带着的序号的,根据序号给出确认序号(告诉发送方下次给我发的序号),发送给发送方,发送方就知道接收方收到了哪些数据
2.超时重传
确认应答是比较理想的情况,但数据在传输过程中,可能是会丢包的
仍以上面例子为例,A 给 B 发消息,你在家嘛?等了很久,A 也没收到 B 的消息,此时,存在以下几种情况:
① B 不想回 A 的消息
② B 没收到 A 的消息 (丢包情况1: 发的请求丢失)
③ B 回复了消息,但 A 没收到 (丢包情况2: 应答的 ACK 丢失)
②③情况:丢包的两种情况,对于发送方来说无法确定是哪种情况,因此,进行统一处理:当发送了一条数据之后,TCP 内部就会自动启动一个定时器,达到一定时间也没收到 ACK,定时器就会自动触发重传消息的动作 —— 超时重传
①情况:
思考:
假设第二次重发没有成功,那么就存在两个超时时间 t1,t2 如图所示:
那么,t1 和 t2 时间一样长吗??
在 TCP 中,t2 会比 t1 更长
TCP 抱着一种 “悲观的态度”,当一次丢包重传之后,TCP 就觉得大概率后面的重传也没用,所以就隔一个更长的时间,节省带宽
上述丢包有两种情况,一种是请求丢失 —— 重传没有问题;一种是 ACK 丢失,重传就意味着接收方收到了相同的数据
TCP 会在内部进行数据去重 (以序号为 key 进行去重),保证应用层读到的数据不是重复数据
确认应答 和 超时重传是 TCP 可靠性中最核心的机制
▲3.1建立连接 - 三次握手
为什么要就建立连接?
1.更好的保证可靠性: 建立连接的过程其实就是让通信双方验证各自的发送能力和接受能力是否正常
2.协商一些重要参数 (如: 序号的初始值)
具体怎样建立连接?
举例:A 给 B 打电话,打电话同样要验证自己以及对方的话筒和听筒是否正常工作
第一次握手: 刚开始,A 不知道自己和 B 手机的听筒和话筒是否正常,所以 A说"喂,你能听到吗?"
第二次握手: B 听到后,说明 A 的话筒和 B 的听筒正常,但 B 还需进一步检查自己的话筒和 A 的听筒是否正常;同时 B 把 A 话筒正常和自己听筒正常的消息传递给 A;于是 B “我能听到,你呢?”
第三次握手: A 收到 B 的消息后,就证明了 A 听筒正常,B 话筒正常
以上三次握手就保证了 A、B 的听筒和话筒都正常,也就保证了通话的正常,这就类似于网络建立连接时的三次握手
TCP 中真实的建立连接过程: (假设主机 A 主动发起连接)
第一次握手: 客户端向服务器发送 SYN 报文 (SEQ=x,SYN=1),并进入 SYN_SENT(传输控制协议) 状态,等待服务器确认
第二次握手: 实际上是分两部分来完成的,即 SYN+ACK (请求和确认) 报文
服务器收到了客户端的请求,向客户端回复一个确认信息 (ack=x+1)
服务器再向客户端发送一个 SYN 包 (SEQ=y)建立连接的请求,此时服务器进入 SYN_RECV 状态
第三次握手: 客户端收到服务器的回复 (SYN+ACK 报文0);此时,客户端也要向服务器发送确认包 (ACK);此包发送完毕客户端和服务器进入 ESTABLISHED 状态,完成 3 次握手建立连接的过程,相当于通信双方各自给对方发送 SYN,在各自给对方发送给 ACK,只不过中间的 ACK 和 SYN 合二为一了,于是最后就是"三次握手"
几个重要的状态:
服务器(B)进程先创建传输控制模块,准备接受客户进程的连接请求。然后服务器进程就处于LISTEN(收听)状态,等待客户的连接请求。如有,即作出响应。
第一次握手:客户(A)进程也是首先创建传输控制模块,然后向B发出连接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序号seq=x。主机B由SYN=1知道,A要求建立联机;TCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但要消耗掉一个序号。这时,TCP客户进程进入SYN-SENT(同步已发送)状态。
第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时服务器(B)进程进入SYN-RCVD(同步收到)状态。(A)客户进程收到B的确认后,还要向B给出确认。
第三次握手:主机A收到后检查ack number是否正确,即确认号:ack=y+1,自己的序号seq=x+1,以及位码ACK是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ACK=1,主机B收到后确认seq=x+1值与ACK=1,TCP连接已经建立,A进入ESTABLISHED(已建立连接)状态。当B收到A的确认后,也进入ESTABLISHED状态。
上面给出的连接建立过程叫做三次握手(Three-way handshake),或三次联络。
位码即tcp标志位,有6种标示:
① SYN(synchronous建立联机);
② ACK(acknowledgement 确认)
③ PSH(push传送)
④ FIN(finish结束)
⑤ RST(reset重置)
⑥ URG(urgent紧急)
Sequence number(顺序号码) //Acknowledge number(确认号码)
sequence number:表示的是我方(发送方)这边,这个packet的数据部分的第一位应该在整个data stream中所在的位置。(注意这里使用的是“应该”。因为对于没有数据的传输,如ACK,虽然它有一个seq,但是这次传输在整个data stream中是不占位置的。所以下一个实际有数据的传输,会依旧从上一次发送ACK的数据包的seq开始)
acknowledge number:表示的是期望的对方(接收方)的下一次sequence number是多少。
注意,SYN/FIN的传输,虽然没有data,但是会让下一次传输的packet seq增加一,但是,ACK的传输,不会让下一次的传输packet seq加一。
LISTEN: 正在侦听来自远方的 TCP 端口的连接请求,服务端启动后处于 LISTEN 状态用于监听不同客户端的 TCP 请求并建立连接
SYN_SEND / SYN_RCVD: 建立连接的中间过程,若连接顺利的话(建立连接过程也可能丢包),这两个状态就一瞬消失
ESTABLISHEN: 连接建立完毕 (验证了通信双方的发送和接受能力都正常),可以进行数据传输▲3.2.断开连接 - 四次挥手
三次握手: 双方各自向对方发起建立连接的请求,再各自给对方回应,只不过,中间的 SYN 和 ACK 能合并在一起
四次挥手: 双方各自向对方发起建立连接的请求,再各自给对方回应,只不过,中间的 FIN 和 ACK 不一定能合并在一起
仍以打电话为例,如下图:
TCP 中真实的断开连接过程: (假设主机 A 主动断开连接)
第一次挥手: 客户端向服务器端发送断开 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状态
1.两次握手可以吗??
不可以,防止已失效的请求报文又传送到了服务端,建立了多余的链接,浪费资源
两次握手只能保证单向连接是通畅的 (为了实现可靠数据传输, TCP 协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的;三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤;如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认)
2.为什么是三次??
主要是为了建立可靠的通信通道,保证客户端与服务端同时具备发送、接收数据的能力
3.四次握手可以吗??
可以,但没必要
四次握手可以验证双方的发送接收能力正常,但是这样做效率比较低
.
两个重要的状态:
CLOSE_WAIT: 表示在等待关闭; 四次挥手挥了一半了,当前可能剩下的两次不挥了(接收方没调用 close 方法,就会导致四次挥手只挥两次,从而没有正确关闭连接)
TIME_WAIT: 谁主动断开连接,谁进入 TIME-WAIT 状态,此时该主机已经完成了四次挥手的过程,但仍不能立刻断开连接,而是要以 TIME-WAIT 状态来保持连接一段时间之后,再彻底释放连接 (处理最后一个 ACK 丢包之后重传的问题)
为了解决网络的丢包和网络不稳定所带来的其他问题,确保连接方能在时间范围内,关闭自己的连接
1.四次挥手,三次挥完行不行??
通常情况下不行,若触发了延时应答机制,就可以三次挥完
"不行",即:上述的 ② ③ 为什么没有合并在一起??
因为中间两次操作的时机不一样
ACK 是收到 FIN 之后立刻由内核返回的数据报,FIN 是应用程序处理完接受缓冲区的数据之后,调用的 close 方法触发的
2.为什么四次??
因为要确保客户端和服务端的数据能够完成传输
3.为什么 TIME_WAIT 状态要等待 2MSL??
假设网络上传输数据的最大时间为 MSL
MSL 就是 ACK / FIN 从主机 A 到主机 B 的最大时间
TIME-WAIT 等待时间,需要分成两个部分:
①等待 ACK 经历一个最大时间到达主机 B
②万一 ACK 丢了,在等待一个最大时间,主机 B 重传 FIN 到达主机 A
因此,TIME_WAIT 就需要等待 2倍的MSL,即:2MSL
原因:确保 ACK 报文能够到达服务端,从而使服务端正常关闭连接
第四次挥手时,客户端第四次挥手的 ACK 报文不一定会到达服务端;服务端会超时重传 FIN / ACK 报文,此时如果客户端已经断开了连接,那么就无法响应服务端的二次请求,这样服务端迟迟收不到 FIN / ACK 报文的确认,就无法正常断开连接
MSL 是报文段在网络上存活的最长时间,客户端等待 2MSL 时间,即「客户端 ACK 报文 1MSL 超时 + 服务端 FIN 报文 1MSL 传输」,就能够收到服务端重传的 FIN / ACK 报文,然后客户端重传一次 ACK 报文,并重新启动 2MSL 计时器;如此保证服务端能够正常关闭
如果服务端重发的 FIN 没有成功地在 2MSL 时间里传给客户端,服务端则会继续超时重试直到断开连接
防止已失效的连接请求报文段出现在之后的连接中
TCP 要求在 2MSL 内不使用相同的序列号;客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以保证本连接持续的时间内产生的所有报文段都从网络中消失;这样就可以使下一个连接中不会出现这种旧的连接请求报文段;或者即使收到这些过时的报文,也可以不处理它
三次握手、四次挥手的常见面试题
有关TCP的连接建立的三次握手及拆除过程的四次挥手的面试问题,是技术面试过程中的出现频率很高的重点和难点问题,常见问题大致如下:
问题(1):为什么关闭连接的需要四次挥手,而建立连接却只要三次握手呢?
关闭连接时,被动断开方在收到对方的FIN结束请求报文时,很可能业务数据没有发送完成,并不能立即关闭连接,被动方只能先回复一个ACK响应报文,告诉主动断开方:“你发的FIN报文我收到了,只有等到我所有的业务报文都发送完了,我才能真正的结束,在结束之前,我会发你FIN+ACK报文的,你先等着”。所以,被动断开方的确认报文,需要拆开成为两步,故总体就需要四步挥手。而在建立连接场景中,服务端端的应答可以稍微简单一些。当服务端端收到客户端端的SYN连接请求报文后,其中ACK报文表示对请求报文的应答,SYN报文用来表示服务端的连接也已经同步开启了,而ACK报文和SYN报文之间,不会有其他报文需要发送,故而可以合二为一,可以直接发送一个SYN+ACK报文。所以,在建立连接时,只需要三次握手即可。
问题(2):为什么连接建立的时候是三次握手,可以改成两次握手吗?
三次握手完成两个重要的功能:一是双方都做好发送数据的准备工作,而且双方都知道对方已准备好;二是双方完成初始SN序列号的协商,双方的SN序列号在握手过程中被发送和确认。
如果把三次握手改成两次握手,可能发生死锁。两次握手的话,缺失了客户端的二次确认ACK帧,假想的TCP建立的连接时二次挥手,可以如下图所示:
图:假想的TCP建立的连接时二次握手的示意图
在假想的TCP建立的连接时二次握手过程中,客户端发送服务端发送一个SYN请求帧,服务端收到后发送了确认应答SYN+ACK帧。按照两次握手的协定,服务端认为连接已经成功地建立了,可以开始发送数据帧。这个过程中,如果确认应答SYN+ACK帧在传输中被丢失,客户端没有收到,客户端将不知道服务端是否已准备好,也不知道服务端的SN序列号,客户端认为连接还未建立成功,将忽略服务端发来的任何数据分组,会一直等待服务端的SYN+ACK确认应答帧。而服务端在发出的数据帧后,一直没有收到对应的ACK确认后就会产生超时,重复发送同样的数据帧。这样就形成了死锁。
问题(3):为什么主动断开方在TIME-WAIT状态必须等待2MSL的时间?
原因之一:主动断开方等待2MSL的时间,是为了确保两端都能最终关闭。假设网络是不可靠的,被动断开方发送FIN+ACK报文后,其主动方的ACK响应报文有可能丢失,这时候的被动断开方处于LAST-ACK状态的,由于收不到ACK确认被动方一直不能正常的进入CLOSED状态。在这种场景下,被动断开方会超时重传FIN+ACK断开响应报文,如果主动断开方在2MSL时间内,收到这个重传的FIN+ACK报文,会重传一次ACK报文,后再一次重新启动2MSL计时等待,这样,就能确保被动断开方能收到ACK报文,从而能确保被动方顺利进入到CLOSED状态。只有这样,双方都能够确保关闭。反过来说,如果主动断开方在发送完ACK响应报文后,不是进入TIME_WAIT状态去等待2MSL时间,而是立即释放连接,则将无法收到被动方重传的FIN+ACK报文,所以不会再发送一次ACK确认报文,此时处于LAST-ACK状态的被动断开方,无法正常进入到CLOSED状态。
原因之二:防止“旧连接的已失效的数据报文”出现在新连接中。主动断开方在发送完最后一个ACK报文后,再经过2MSL,才能最终关闭和释放端口,这就意味着,相同端口的新TCP新连接,需要在2MSL的时间之后,才能够正常的建立。2MSL这段时间内,旧连接所产生的所有数据报文,都已经从网络中消失了,从而,确保了下一个新的连接中不会出现这种旧连接请求报文。
问题(4):如果已经建立了连接,但是客户端端突然出现故障了怎么办?
TCP还设有一个保活计时器,客户端端如果出现故障,服务端端不能一直等下去,这样会浪费系统资源。每收到一次客户端客户端的数据帧后,服务端端都的保活计时器会复位。计时器的超时时间通常是设置为2小时,若2小时还没有收到客户端端的任何数据帧,服务端端就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务端端就认为客户端端出了故障,接着就关闭连接。如果觉得保活计时器的两个多小时的间隔太长,可以自行调整TCP连接的保活参数。