# 传输层 TCP 协议
# TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
# 特点
TCP 是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:
- 基于流的方式
- 面向连接
- 可靠通信方式
- 在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销
- 通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点
# 协议规定
- 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由 TCP 确定分片的大小并控制分片和重组;
- 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
- 滑动窗口:TCP 使用的流量控制协议是可变大小的滑动窗口协议。TCP 连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP 在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
- 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
- 失序处理:作为 IP 数据报来传输的 TCP 分片到达时可能会失序,TCP 将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
- 重复处理:作为 IP 数据报来传输的 TCP 分片会发生重复,TCP 的接收端必须丢弃重复的数据;
- 数据校验:TCP 将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP 将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
# 报文首部
TCP 的固定包头为 20 个字节,每一行 32bit(4Byte),5行。
# 第一行:源端口与目的端口
根据 OSI 七层模型我们知道 TCP 属于传输层,IP 属于网络层,最终数据都在物理层上传输,其中从传输层到网络层会在发送数据前会封装 IP 首部,表示要传给那台 IP 地址的机器。然后我们需要知道端口的概念:端口可以认为是 设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口。我们这里指的源端口与目的端口是指虚拟端口的网络端口。根据计算机网络的知识我们知道端口对多有 65535 个。也就是 16 个二进制位。因此源端口与目的端口各 16 bit,占了一行。
# 第二行:序号
TCP 中每传输的一个数据都有一个序号。因为如果数据太大,我们会将数据切割成不同的数据报,有序号后就不会因为数据包在网络停留时间不一而导致的接受顺序不一样使得数据出现乱序。序号占用 32 bit,当序号超出 2^32-1 的话从 0 重新开始记。
# 第三行:确认号
表示期望下一次发送数据。如果确认号为N,那么表示发送该报文的机器已经接受到了N-1及其以前的数据。
# 第四行:数据偏移,保留,URG,ACK,PSH,RST,SYN,窗口
数据偏移
是指 TCP 数据起始位置距离 TCP 报文起始位置的距离,一般情况下为报文首部长度 20 字节,但 TCP 首部有一个可选长度要注意。
保留
很明显就是现在没用,留给以后使用
URG
当URG=1,表示该报文是紧急报文,因此发送方会将该报文放在最开头传输,要配合TCP首部紧急指针一起用
ACK
ACK=1 时确认号字段才有效,TCP 规定,连接建立后所有传送的报文段都必须把 ACK 置为 1。
PSH
当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP 就可以使用推送操作
RST
当 RST=1 时,表明 TCP 连接中出现严重错误,必须释放连接,然后再重新建立运输连接
SYN
在连接建立时用来同步序号,当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文段。对方若同意时,则应在响应的报文段中使 SYN=1 和 ACK=1,因此,SYN 置 1 就表示这是一个连接请求或连接接受报文。FIN,用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放运输连接。
窗口
占 2 个字节,窗口指的是发送本报文段的一方的接收窗口,不是自己的发送窗口,告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量。窗口值作为接受方让发送方设置其发送窗口的依据,一般用来平衡双方的数据传输速率(带宽)不一致问题。
# 第五行:校验和紧急指针
校验和
占 2 字节。校验和字段检验的范围包括首部和数据这两部分。
紧急指针
占 2 个字节,紧急指针仅在 URG=1 时才有意义,它指出本报文段中的紧急数据的字节数。当所有紧急数据处理完毕时,TCP 就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为 0 时也可发送紧急数据。
# 优点
- TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。
- 对可靠性要求高的通信系统往往使用 TCP 传输数据。
- 但是由于各种数据校验机制,导致工作效率较 UDP 来说较低
# TCP 三次握手
# 目的
为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。
# 热身
# SYN
同步序列编号(Synchronize Sequence Numbers)。是 TCP/IP 建立连接时使用的握手信号。是 TCP 连接的第一个包,非常小的一种数据包。SYN 攻击包括大量此类的包,由于这些包看上去来自实际不存在的站点,因此无法有效进行处理。每个机器的欺骗包都要花几秒钟进行尝试方可放弃提供正常响应。
# ACK
确认字符 (Acknowledge character)。在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。在 TCP/IP 协议中,如果接收方成功的接收到数据,那么会回复一个 ACK 数据。通常 ACK 信号有自己固定的格式,长度大小,由接收方回复给发送方。
# seq
是数据包本身的序列号
# ack
是对收到的数据包的确认,值是下次希望接收的数据包的序列号。
# seq 和 ack
在第一次消息发送中,A 随机选自取一个序列号作为自己的初始序号(seq=x)发送给 B;第二次消息 B 使用 ack 对 A 的数据包进行确认,因为已经收到了序列号为 x 的数据包,正准备接收序列号为 x+1 的包,所以 ack=x+1,同时 B 告诉 A 自己的初始序列号,就是seq=y;第三度条消息 A 告诉 B 收到了 B 的确认消息并准备建立连接,A 自己此条消息的序列号是 x+1,所以 seq=x+1,而 ack=y+1 是表示知 A 正准备接收 B 序列号为 y+1 的数据包。
# 具体步骤
- 第一步,Client 会进入 SYN_SENT 状态,并发送 Syn 消息给 Server 端,SYN 标志位在此场景下被设置为1,同时会带上 Client 这端分配好的 Seq 号,这个序列号是一个 U32 的整型数,该数值的分配是根据时间产生的一个随机值,通常情况下每间隔 4ms 会加 1。除此之外还会带一个 MSS,也就是最大报文段长度,表示 TCP 传往另一端的最大数据块的长度。
- 第二步,Server 端在收到 Syn 消息之后,会进入 SYN_RCVD 状态,同时返回 Ack 消息给 Client,用来通知 Client,Server 端已经收到 SYN 消息并通过了确认。这一步 Server 端包含两部分内容,一部分是回复 Client 的 Syn 消息,其中 ACK=1,Seq 号设置为Client 的 Syn 消息的 Seq 数值 +1;另一部分是主动发送 Sever 端的 Syn 消息给 Client,Seq 号码是 Server 端上面对应的序列号,当然 Syn 标志位也会设置成 1,MSS 表示的是 Server 这一端的最大数据块长度。
- 第三步,Client 在收到第二步消息之后,首先会将 Client 端的状态从 SYN_SENT 变换成 ESTABLISHED,此时 Client 发消息给 Server 端,这个方向的通道已经建立成功,Client 可以发送消息给 Server 端了,Server 端也可以成功收到这些消息。其次,Client 端需要回复 ACK 消息给 Server 端,消息包含 ACK 状态被设置为 1,Seq 号码被设置成 Server 端的序列号 +1。(备注:这一步往往会与 Client 主动发起的数据消息,合并到一起发送给 Server 端。)
- 第四步,Server 端在收到这个 Ack 消息之后,会进入 ESTABLISHED 状态,到此时刻 Server 发向 Client 的通道连接建立成功,Server 可以发送数据给 Client。
第一次握手
客户端向服务端发送连接请求报文段。该报文段的头部中 SYN=1,ACK=0,同时选择一个初始序号 seq=x。请求发送后,客户端便进入SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,会发送一个应答:SYN=1,ACK=1,seq=y,ack=x+1。发送完应答后服务端进入SYN-RCVD 状态。
第三次握手
客户端收到服务端连接同意的应答后,还会向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。该报文段的头部为:ACK=1,seq=x+1,ack=y+1。该报文发送完毕后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。
# 重要概念
# 未连接队列
在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的 SYN 包(seq=j)开设一个条目,该条目表明服务器已收到 SYN 包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于 SYN_RECV 状态,当服务器收到客户的确认包时,删除该条目,服务器进入 ESTABLISHED 状态。
# Backlog 参数
表示内核为相应套接字排队的最大连接个数。SYN-ACK 重传次数服务器发送完 SYN-ACK 包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同。
# 半连接存活时间
半连接队列的条目存活的最长时间,也即服务器从收到SYN包到确认这个报文无效的最长时间,该时间值是所有重传请求包的最长等待时间总和。有时我们也称半连接存活时间为 Timeout 时间、SYN_RECV 存活时间
# 常见问题
# Q1. 为什么要握三次,而不是两次或四次?
首先非常明确的是两次握手是最基本的。三次握手是为了防止已失效的连接请求报文段突然又传送到了服务端,造成服务端资源的浪费。不是四次握手:因为三次握手已经能说明握手时的通信是正常的,四次握手、五次握手就显得浪费了。
第一次握手,客户端发了个连接请求消息到服务端,服务端收到信息后知道自己与客户端是可以连接成功的,但此时客户端并不知道服务端是否已经接收到了它的请求。
第二次握手,服务端接收到消息后的应答,客户端得到服务端的反馈后,才确定自己与服务端是可以连接上的, 客户端只有确定了自己能与服务端连接上才能开始发数据。所以两次握手肯定是最基本的。
第三次握手,是为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误。譬如发起请求遇到类似这样的情况:客户端发出去的第一个连接请求由于某些原因在网络节点中滞留了导致延迟,直到连接释放的某个时间点才到达服务端,这是一个早已失效的报文,但是此时服务端仍然认为这是客户端的建立连接请求第一次握手,于是服务端回应了客户端,第二次握手。如果只有两次握手,那么到这里,连接就建立了,但是此时客户端并没有任何数据要发送,而服务端还在傻傻的等候佳音,造成很大的资源浪费。所以需要第三次握手,只有客户端再次回应一下,就可以避免这种情况。
# Q2. 如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次。若一连发送 10个 探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
# TCP 四次挥手
TCP连接是双向的,在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。
# 具体步骤
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为 seq=u(等于前面已经传送过来的数据的最后一个字节的序号加 1),此时,客户端进入 FIN-WAIT-1(终止等待1)状态。 TCP 规定,FIN 报文段即使不携带数据,也要消耗一个序号。
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号 seq=v,此时,服务端就进入了 CLOSE-WAIT(关闭等待)状态。TCP 服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入 FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为 seq=w,此时,服务器就进入了 LAST-ACK(最后确认)状态,等待客户端的确认。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是 seq=u+1,此时,客户端就进入了 TIME-WAIT(时间等待)状态。注意此时 TCP 连接还没有释放,必须经过 2MSL(最长报文段寿命)的时间后,当客户端撤销相应的 TCB(传输控制块)后,才进入 CLOSED 状态。
服务器只要收到了客户端发出的确认,立即进入 CLOSED 状态。同样,撤销 TCB 后,就结束了这次的 TCP 连接。可以看到,服务器结束 TCP 连接的时间要比客户端早一些。
第一次挥手
客户端数据发送完成,则它向服务端发送连接释放请求。该请求只有报文头,头中携带的主要参数为:FIN=1,seq=u。此时,客户端将进入 FIN-WAIT-1 状态。TCP 规定,FIN 报文段即使不携带数据,也要消耗一个序号。
第二次挥手
服务器收到客户端连接释放报文,通知相应的高层应用进程,告诉它客户端向服务器这个方向的连接已经释放了。此时服务端进入了CLOSE-WAIT(关闭等待)状态,并向客户端发出连接释放的应答,其报文头包含:ACK=1,ack=u+1,seq=v。
客户端收到该应答后,进入FIN-WAIT-2状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
第二次挥手完成后,客户端到服务端方向的连接已经释放,服务端不会再接收客户端的数据,客户端也没有数据要发送了。但服务端到客户端方向的连接仍然存在,服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
第三次挥手
服务端将最后的数据发送完毕后,就向客户端发送连接释放报文,其报文头包含:FIN=1,ack=u+1,由于在 CLOS-WAIT 状态,服务端很可能又发送了一些数据,假定此时的序列号为 seq=w,此时,服务器就进入了 LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手
客户端收到服务器的连接释放报文后,向服务端发出确认应答,报文头:ACK=1,ack=w+1,seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。该状态会持续 2MSL(最长报文段寿命)时间,这个期间 TCP 连接还未释放,若该时间段内没有服务端的重发请求的话,客户端就进入 CLOSED 状态,服务端只要收到了客户端发出的确认,立即进入 CLOSED 状态。就结束了这次的 TCP 连接。可以看到,服务器结束 TCP 连接的时间要比客户端早一些。
# 常见问题
# Q1. 为什么连接的时是三次握手,关闭的时却是四次握手或者说四次挥手?
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
# Q2. 为什么客户端最后还要等待 2MSL?
MSL 最大分段寿命(Maximum Segment Lifetime),是一个 TCP 分段可以存在于互联网系统中的最大时间。TCP 允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个 ACK 报文能够到达服务器。因为这个 ACK 报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK 报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个 2MSL 时间段内收到这个重传的报文,接着给出回应报文,并且会重启 2MSL 计时器。
第二,防止类似与 三次握手 中提到了的 已经失效的连接请求报文段 出现在本连接中。客户端发送完最后一个确认报文后,在这个 2MSL 时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文
# TCP 与 UDP
- TCP 面向连接(TCP 三次握手),UDP 是无连接的,即发送数据之前不需要建立连接。
- 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信。
- TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP是面向报文的。
- TCP 有序,UDP 无序;消息在传输过程中可能会乱序,后发送的消息可能会先到达,TCP 会对其进行重排序,UDP不会。
- TCP 有流量控制(拥塞控制),UDP 没有。
- TCP 传输速率慢,由于传输要进行三次握手,以及会进行拥塞控制等。
- TCP 应用场景:效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP 高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。UDP 应用场景:效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ 聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。
流量控制
TCP 利用滑动窗口机制在 TCP 连接上实现对发送方的流量控制, 如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。
拥塞控制
防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。当出现网络抖动时,TCP 会自觉降低发送速度,他会努力维护次序,但 UDP 依然保持速度不变。TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付。
TCP | UDP | |
---|---|---|
连接 | 面向连接 | 无连接 |
连接方式 | 点到点 | 支持一对一,一对多,多对一和多对多 |
模式 | 流模式(TCP) | 数据报模式(UDP) |
头部大小 | 20字节 | 8字节 |
有序性 | 有序 | 无序 |
传输速率控制 | 流量控制(拥塞控制) | 无 |
可靠性 | 可靠 | 不可靠 |
传输速率 | 慢 | 快 |
占用系统资源 | 高 | 低 |
应用场景 | 效率要求相对低,但对准确性要求相对高的场景。 | 效率要求相对高,对准确性要求相对低的场景 |
TCP 字节流和 UDP 数据报区别
两者的区别在于 TCP 接收的是一堆数据,而每次取多少由主机决定。而 UDP 发的是数据报,客户发送多少就接收多少。
拥有这些区别的原因是由于 TCP 和 UDP 的特性不同而决定的。TCP 是面向连接的,也就是说,在连接持续的过程中,socket中收到的数据都是由同一台主机发出的,因此,知道保证数据是有序的到达就行了,至于每次读取多少数据自己看着办。 而UDP是无连接的协议,也就是说,只要知道接收端的IP和端口,且网络是可达的,任何主机都可以向接收端发送数据。这时候,如果一次能读取超过一个报文的数据,则会乱套。比如,主机 A 向发送了报文 P1,主机 B 发送了报文 P2,如果能够读取超过一个报文的数据,那么就会将 P1 和 P2 的数据合并在了一起,这样的数据是没有意义的。
← 网络通信模型 WebSocket 协议 →