TCP 与 UDP

TCP

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的可靠的基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接。

TCP数据报结构

TCP 与 UDP

  • ***Seq(Sequence Number):占 32 位,用来标识从 计算机A 发送到 计算机B 的数据包的序号,发送的数据是被拆成多个数据包发送,***对每个数据包进行编号,接收方根据***对数据包进行拼接。(初始***是随即生成的)
  • 确认号Ack(Acknowledge Number):占 32 位,Ack = Seq + 1,代表本端下一个要接收的数据包的编号。
  • 标志位:每个标志位占用 1 bit,共有 6 个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下:
    • SYN:请求建立一个新连接。
    • FIN:请求断开一个连接,一个 FIN 只对应关闭一个方向的数据传输。
    • ACK:代表确认序号有效,确认接收。
    • URG:代表紧急指针(urgent pointer)有效。
    • PSH:表示接收方应该尽快将这个报文交给应用层。
    • RST:重置连接。
TCP连接的建立(三次握手)

TCP连接的建立要传输三个数据包,称为三次握手(Three-way Handshaking)。

使用 connect() 建立连接时,客户端和服务器端会相互发送三个数据包,请看下图:
TCP 与 UDP
客户端调用 socket() 函数创建套接字后,因为没有建立连接,所以套接字处于 CLOSED 状态;服务器端调用 listen() 函数后,套接字进入 LISTEN 状态,开始监听客户端请求。
这个时候,客户端开始发起请求:

  • 当客户端调用 connect() 函数后,TCP协议会组建一个数据包,并设置 SYN 标志位,表示该数据包是用来建立同步连接的。同时生成一个随机数字,填充“序号(Seq)”字段,表示该数据包的序号。完成这些工作,向服务器端发送数据包,然后客户端进入了 SYN-SEND 状态。
  • 服务器端收到数据包,检测到设置了 SYN 标志位,就知道这是客户端发来的建立连接的“请求包”,服务器端也会组建一个数据包。
    • 设置 SYN 和 ACK 标志位,SYN 表示该数据包用来建立连接,ACK 用来确认收到了刚才客户端发送的数据包。
    • 生成一个随机数填充“序号(Seq)”字段,该序号客户端数据包没有关系。
    • 将客户端数据包序号(1000)加1,得到1001,并用这个数字填充“确认号(Ack)”字段。
    • 服务器将数据包发出,并进入 SYN-RECV 状态。
  • 客户端收到数据包,检测到已经设置了 SYN 和 ACK 标志位,就知道这是服务器发来的“确认包”。客户端会检测“确认号(Ack)”字段,看它的值是否为 1000+1,如果是就说明连接建立成功。接下来,客户端会继续组建数据包,并设置 ACK 标志位,表示客户端正确接收了服务器发来的“确认包”。同时,将刚才服务器发来的数据包序号(2000)加1,得到 2001,并用这个数字来填充“确认号(Ack)”字段。客户端将数据包发出,进入 ESTABLISED 状态,表示连接已经成功建立。
  • 服务器端收到数据包,检测到已经设置了 ACK 标志位,就知道这是客户端发来的“确认包”。服务器会检测“确认号(Ack)”字段,看它的值是否为 2000+1,如果是就说明连接建立成功,服务器进入 ESTABLISED 状态。

至此,客户端和服务器都进入了 ESTABLISED 状态,连接建立成功,接下来就可以收发数据了。

三次握手的关键是要确认对方收到了自己的数据包,这个目标就是通过“确认号(Ack)”字段实现的。计算机会记录下自己发送的数据包序号 Seq,待收到对方的数据包后,检测“确认号(Ack)”字段,看Ack = Seq + 1是否成立,如果成立说明对方正确收到了自己的数据包。

为什么需要第三次握手?
TCP 与 UDP

SYN泛洪攻击

  • 防范措施
    • 降低SYN timeout,使主机尽快释放半连接的占用。
    • 采用SYN cookie设置,短时间内连续收到某个IP重复的SYN,则认为受到该IP的攻击,丢弃来自该IP的后续请求报文。
    • 网关出设置过滤。
TCP数据的传输过程

建立连接后,两台主机就可以相互传输数据了。如下图所示:
TCP 与 UDP
上图给出了主机A分2次(分2个数据包)向主机B传递200字节数据的过程。首先,主机A通过1个数据包发送100个字节的数据,数据包的 Seq 号设置为 1200。主机B为了确认这一点,向主机A发送 ACK 包,并将 Ack 号设置为 1301。
为了保证数据准确到达,目标机器在收到数据包(包括SYN包、FIN包、普通数据包等)包后必须立即回传ACK包,这样发送方才能确认数据传输成功。
此时 Ack 号为 1301 而不是 1201,原因在于 Ack 号的增量为传输的数据字节数。假设每次 Ack 号不加传输的字节数,这样虽然可以确认数据包的传输,但无法明确100字节全部正确传递还是丢失了一部分,比如只传递了80字节。因此按如下的公式确认 Ack 号:Ack号 = Seq号 + 传递的字节数 + 1
与三次握手协议相同,最后加 1 是为了告诉对方要传递的 Seq 号。

下面分析传输过程中数据包丢失的情况,如下图所示:
TCP 与 UDP
上图表示通过 Seq 1301 数据包向主机B传递100字节的数据,但中间发生了错误,主机B未收到。经过一段时间后,主机A仍未收到对于 Seq 1301 的ACK确认,因此尝试重传数据。

超时重传

为了完成数据包的重传,TCP套接字每次发送数据包时都会启动定时器,如果在一定时间内没有收到目标机器传回的 ACK 包,那么定时器超时,数据包会重传。

  • 超时重传时间(RTO, Retransmission Time Out):这个值太大了会导致不必要的等待,太小会导致不必要的重传,理论上最好是网络 RTT 时间,但又受制于网络距离与瞬态时延变化,所以实际上使用自适应的动态算法(例如 Jacobson 算法和 Karn 算法等)来确定超时时间。
  • 往返时间(RTT,Round-Trip Time):表示从发送端发送数据开始,到发送端收到来自接收端的 ACK 确认包(接收端收到数据后便立即确认),总共经历的时延。
  • 重传次数:TCP数据包重传次数根据系统设置的不同而有所区别。有些系统,一个数据包只会被重传3次,如果重传3次后还未收到该数据包的 ACK 确认,就不再尝试重传。但有些要求很高的业务系统,会不断地重传丢失的数据包,以尽最大可能保证业务数据的正常交互。

最后需要说明的是,发送端只有在收到对方的 ACK 确认包后,才会清空输出缓冲区中的数据。

快速重传

当后面的序号先到达,如接收方收到 1、3 数据包,而 2 未收到,则会立即向发送方重复发送三次 Ack = 2 的确认包请求重传。如果发送方收到三个相同Ack的确认包,则会重传该数据包,无需等待超时重传。

TCP连接的断开(四次挥手)

建立连接非常重要,它是数据正确传输的前提;断开连接同样重要,它让计算机释放不再使用的资源。如果连接不能正常断开,不仅会造成数据传输错误,还会导致套接字不能关闭,持续占用资源,如果并发量高,服务器压力堪忧。

建立连接需要三次握手,断开连接需要四次挥手。

为什么需要四次挥手?
由于TCP连接是全双工的,每个传输方向都必须要单独关闭,所以需要两次关闭请求和两次确认。

下图演示了客户端主动请求断开连接的场景:
TCP 与 UDP

建立连接后,客户端和服务器都处于ESTABLISED状态。这时,客户端发起断开连接的请求:

  • 客户端调用 close() 函数后,向服务器发送 FIN 数据包,进入 FIN_WAIT_1 状态。
  • 服务器收到数据包后,检测到设置了 FIN 标志位,知道要断开连接,于是向客户端发送“确认包”,进入 CLOSE_WAIT 状态。(注意:服务器收到请求后并不是立即断开连接,而是先向客户端发送“确认包”,告诉它我知道了,我还需要准备一下才能断开连接。)
  • 客户端收到“确认包”后进入 FIN_WAIT_2 状态,等待服务器准备完毕(此时客户端 -> 服务端 的传输方向关闭)。
  • 等待片刻后,服务器准备完毕,可以断开连接,于是主动向客户端发送 FIN 包请求断开连接,然后服务器进入 LAST_ACK 状态。
  • 客户端收到服务器的 FIN 包后,向服务器发送 ACK 包,然后进入 TIME_WAIT 状态。
  • 服务器收到客户端的 ACK 包后,断开连接,关闭套接字,进入 CLOSED 状态。
  • 客户端经过 2MSL 的时间后进入 CLOSED 状态,至此TCP连接断开。

关于 TIME_WAIT 状态的说明
客户端最后一次发送 ACK包 后进入 TIME_WAIT 状态,而不是直接进入 CLOSED 状态关闭连接,这是为什么呢?
TCP 是面向连接的传输方式,必须保证数据能够正确到达目标机器,不能丢失或出错,而网络是不稳定的,随时可能会毁坏数据,所以机器A每次向机器B发送数据包后,都要求机器B”确认“,回传ACK包,告诉机器A我收到了,这样机器A才能知道数据传送成功了。如果机器B没有回传ACK包,机器A会重新发送,直到机器B回传ACK包。
客户端最后一次向服务器回传ACK包时,有可能会因为网络问题导致服务器收不到,服务器会再次发送 FIN 包,如果这时客户端已经关闭了连接,那么服务器无论如何也收不到ACK包了,所以客户端需要等待片刻、确认对方收到ACK包后才能进入CLOSED状态。那么,要等待多久呢?
数据包在网络中是有生存时间的,超过这个时间还未到达目标主机就会被丢弃,并通知源主机。这称为报文最大生存时间(MSL,Maximum Segment Lifetime)
TIME_WAIT 要等待 2MSL 才会进入 CLOSED 状态。ACK 包到达服务器需要 MSL 时间,服务器重传 FIN 包也需要 MSL 时间,2MSL 是数据包往返的最大时间,如果 2MSL 后还未收到服务器重传的 FIN 包,就说明服务器已经收到了 ACK 包。

TCP流量控制:滑动窗口协议

1、全双工协议:A 与 B 分别维护者一个独立的发送缓冲区和接收缓冲区。
2、以 A发B收 为例

  • 对于 A:
    • A 的发送缓冲区【已发送并收到确认 | 已发送未收到确认 | 未发送且允许发送 | 不允许发送】
    • A 的发送窗口是发送缓冲区的一部分,其中包括【已发送未收到确认 | 未发送且允许发送】
    • A 发送的数据得到 B 确认之后,才会移动发送窗口。
  • 对于 B:
    • B 会确认收到的连续的数据分组,对于乱序的分组会先接收,后面要求 A 重发。
    • 所谓流量控制,是接收方B传递信息给发送方A,告知自己接收窗口的大小。发送方A会根据B的信息调整自己的发送窗口大小。当 B 传递信息表示自己接收窗口为 0 时,A 不能再发送。
  • 死锁:B 告诉 A 自己的接收窗口为 0,A 停止发送数据。当 B 又可接收时,发送消息通知 A,但此消息丢失,于是造成 A 等 B 通知,B 等 A 发送数据的情形,形成死锁。
  • 死锁的解决:引入持续计时器(Persistence timer),当 A 收到 B 的零窗口通知时,启动该计时器,到时会发送一个1字节的探测报文,对方会回应自身此时的接收窗口大小,若仍为0则 A 重设计时器,继续等待。

3、流量控制中的传输效率问题

  • 一次发送一个字节或者接收方一有一个空余就通知发送方发送,都会增加网络中不必要的报文。(比如1个字节的数据却带着40字节的头部)
  • 对于发送方A,一次要尽可能地多发送数据,广泛采用 Nagle 算法:
    • A 先发送1个字节,把后面要发送的数据缓存起来。
    • 收到 B 的回复后,也得到了网络情况和对方的窗口大小,再把缓冲区的数据组成合适大小的报文发送。
    • 当到达的数据达到发送窗口大小的一半,或已达到报文段最大长度(MSS)时,立即发送一个报文段。
  • 对于接收方B,往往是让 B 等待一段时间,等到有足够空间容纳一个报文段或接收窗口有一半空间时,再通知发送方发送。
TCP拥塞控制

慢开始、拥塞避免、快重传、快恢复

判断拥塞的标志:

  • 重传计时器超时。
  • 接收到三个重复确认。

https://www.cnblogs.com/wuchanming/p/4422779.html

TCP 与 UDP
  • TCP基于连接,UDP无连接。
  • TCP保证了数据的顺序,UDP不保证。
  • TCP数据传输具有可靠性(确认和重传机制),而UDP是不可靠的。
  • TCP具有拥塞控制、滑动窗口等机制保证传输的质量,而UDP没有。
  • TCP数据传输基于字节流,应用程序交给TCP的数据长度太长超过MSS时,TCP会进行分段,因此也称TCP的数据无边界;UDP基于报文,无论应用程序交给UDP多长的数据,UDP都不会进行拆分,因此UDP保留了应用层数据的边界。

https://blog.****.net/Li_Ning_/article/details/52117463

应用层协议

  • 基于TCP:HTTP(80)、FTP(20、21)、Telnet、SMTP、POP3(110)、HTTPS
  • 基于UDP:DNS(53)、SNMP、NFS