TCP UDP记录

32 阅读12分钟

UDP

UDP的报文头

image.png 源、目的端口:说明该报文来自哪个端口去往哪个端口。
包长度:记录了这个UDP报文的总长度。
校验和:校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中受损的 UDP 包。

UDP的基础介绍

UDP特点:

  1. 无连接:udp不在点和点直接建立连接,当想发送数据的时候就会发送。
  2. 不可靠:udp提供尽力的传输服务,无法保证发送数据的完整性和安全性
  3. 基于报文:应用层传输下来的完整数据,udp不会做任何处理就放入udp报文中传输。

应用场景

由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNS 、SNMP 等;
  • 视频、音频等多媒体通信;
  • 广播通信;

TCP

tcp的报文头

image.png 源端口、目的端口:和udp一样。
序列号:建立连接时就会初始化,随机数字,用于标记报文段,通过序列号可以实现不接收重复报文段和报文段的顺序接收。
确认应答号:用于表示自己已经接收到了哪里,返回值为期望下次接收到的报文段的序列号。 校验和:和udp一样。 控制位:都是置1时才启动。

  1. ACK:确认控制位,置1时才能使用确认应答号。除了第一次连接的报文外都要置1。
  2. RST:使用说明出现了严重错误,要重启连接。
  3. SYN:表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  4. FIN:表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

应用场景

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输;
  • HTTP / HTTPS;

TCP详解

TCP 特性

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

面向连接:在通信前要建立通信才行,并且tcp是一对一的连接方式。
可靠性:tcp根据一系列措施保证数据的安全性、完整性顺序性等问题。
字节流:udp是接收到上层的文件就直接打包发送而tcp则要将数据分成字节再传输。

image.png

TCP连接的三次握手

  • 首先通信的双方即客户端和服务端都处于CLOSE状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
  • 客户端想建立通信时会先初始化一个序列号放入报文的序列号处,同时将SYN置为1,表示这是一个SYN报文,之后将报文发送出去,之后客户端处于SYN-SENT状态。第一次握手
  • 服务端收到之后客户端的SYN报文后也会初始化一个序列号,将该序列号放入报文头的序列号处,将收到的SYN报文的序列号值加一作为报文头确认应答号的内容。将状态位SYN和ACK置为1,之后就将该报文发送过去,之后服务端处于 SYN-RCVD 状态。第二次握手
  • 客户端收到服务端发送过来的报文后用同样的方法生成报文头的确认应答号的内容,将ACK置为1,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。第三次握手
  • 第三次握手时可以携带数据而前两次握手不能携带。
  • 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。

握手相关问题

为什么要三次握手?

  1. 三次握手才能保证双方具有接收和发送的能力。第一次握手证明客户端发送能力,第二次说明服务端的接收发送能力,第三次证明客户端的接收能力。
  2. 避免历史连接,防止「历史连接」初始化了连接。 有这么个情况:客户端先发送了 SYN(seq = 90)报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100)报文。 客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:
  • 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
  • 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文。
  • 服务端收到 RST 报文后,就会释放连接。
  • 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。

如果是两次握手连接,就无法阻止历史连接,因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。如果采用两次握手建立 TCP 连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接,导致服务端建立了一个历史连接,又白白发送了数据,妥妥地浪费了服务端的资源

  1. 同步双方初始化序列号。 客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
  2. 避免资源浪费。如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接,造成资源的浪费。

为什么建立连接时要随机初始化序列号

如果每次建立连接的时候都初始化同一组号就容易出现下面的问题。

  1. 历史报文(旧报文)会被新连接接收,造成数据混乱。
  2. 防止黑客伪造相同的报文。

SYN攻击

先说两个概念:TCP 半连接和全连接队列。
在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;

  • 全连接队列,也称 accept 队列;

image.png 刚开始连接的时候会创建一个半连接对象放入SYN队列中,当该对象收到正确的ACK后放入全连接队列。 应用在使用连接时调用accept()调用全连接对象。

SYN攻击就是在SYN这块使坏。攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
避免 SYN 攻击方式,可以有以下四种方法:

  • 调大 netdev_max_backlog;
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;
  • 减少 SYN+ACK 重传次数

TCP连接的四次挥手

连接的双方都可以主动关闭连接,描述以客户端主动关闭为例。过程:

  1. 客户端要关闭连接,想服务端发送FIN报文,之后进入TIME_WATI1状态。
  2. 服务端收到客户端的FIN报文,回复ACK确认报文,进入CLOSE_WAIT状态。此时因为服务端的数据可能没有发送完,所以并不会立刻发送FIN报文。
  3. 客户端收到ACK报文后进入TIME_WAIT2状态。
  4. 服务端数据发送完毕,就会向客户端发送FIN报文。进入LAST_ACK状态。
  5. 客户端收到FIN报文,回复ACK确认报文进入TIME_WAIT状态,等待2MSL时间无问题后自动进入CLOSE状态。
  6. 服务端收到ACK报文后进入COLSE状态。

主动关闭的一方才有TIME_WAIT状态。

挥手相关问题

为什么 TIME_WAIT 等待的时间是 2MSL?

网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

为什么需要TIME_WAIT状态

  1. 防止旧数据被新连接接收。如果没有TIME_WAIT状态或过短,第一次TCP连接的因为延迟没被接收的数据可能会在第二次同四元组TCP连接接收。因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
  2. 保证「被动关闭连接」的一方,能被正确的关闭。等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。避免丢失ACK报文而导致FIN报文重发后客户端已关闭而返回RST异常关闭这种类似情况。

TCP重传策略

  1. 超时重传:发送数据后如果超过了设置的时间还没有收到确认报文就会启动超时重传机制。将没有收到的确认报文的对应的报文再发过去。
  2. 快速重传:只依靠超时重传效率还是太差,快速重传就解决了这种问题。在发送端收到三个相同的确认报文发送端就会立刻重新发送一份报文过去。但这样就不清楚对方接收了多少报文。发送端该重发多少报文。
  3. SACK方法,通过SACK方法告诉发送方哪些数据被接收到了。
  4. D-SACK方法:Duplicate SACK ,通过SACK方法告诉对方收到了重复的报文。D-SACK 有这么几个好处:

(1)可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
(2)可以知道是不是「发送方」的数据包被网络延迟了;
(3)可以知道网络中是不是把「发送方」的数据包给复制了;

滑动窗口

因为确认收到一个报文就再发送一个新的效率过于底下,所以就引入了滑动窗口。 那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

TCP 头里有一个字段叫 Window,也就是窗口大小。

这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

流量控制

通信双方根据接收方的可用窗口大小来调节发送的数据。

拥塞控制

拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。 如何判断发生了拥塞?出现了超时重传。 主要的四个算法:

  1. 慢启动:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加1。呈现指数增长。有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。
  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用「拥塞避免算法」。
  1. 拥塞避免算法:每当收到一个 ACK 时,cwnd 增加 1/cwnd。呈现线性增长。
  2. 拥塞发生:分两种情况:超时重传和快速重传。
    超时重传:ssthresh 和 cwnd 的值会发生变化: ssthresh 设为 cwnd/2, cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)。 之后重新开始慢启动。 快速重传:ssthresh 和 cwnd 变化如下:
  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法
  1. 快速恢复算法:进入快速恢复算法如下:
  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;