传输层

传输层

传输层

image-20240527151114201

端口号

端口的由来:

在操作系统中,不同的进程是通过进程标识符(pid)进行区分。不同的操作系统使用的pid的格式不尽相同,但在网络里我们需要采用统一的格式进行区分。因此端口号诞生了。它采用统一的格式来标识进程。

端口号的格式:TCP/IP: 16bit位的正整数

c++:uint16_t

在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信。

端口的分类

服务端使用的端口号

  • 熟知端口号: 里面一些端口固定绑定某些应用程序:1-1023

    应用程序HTTPSSHFTP
    端口号802221
  • 登记端口号:

  • 客户端端口号:49152-65535,客户端运行时,操作系统动态绑定,通信时自动绑定,通信结束自动回收

端口号的理解

传输层的协议由TCP/UDP,这里2个协议能公用一个端口号?可以

首先我们要理解端口号对于传输层的作用:区分同一个主机上不同应用程序的数据包

当主机收到一个IP数据包,根据IP数据包里的协议号分辨是TCP还是UDP,然后根据这一信息,交给系统中的TCP或UDP模块进行处理,然后TCP或UDP模块再端口号转发给对应的应用程序。

UDP(user datagram protocol)

用户数据报协议

UDP特点:

  • 无连接
  • 不可靠,尽最大努力交付,因此数据可能丢失、乱序
  • 面向数据报,每次读取都是完整的报文,且发多少个数据报,就要收到多少个数据报
  • UDP没有拥塞控制,因此网络拥塞不会降低发送速率
  • UDP支持一对一,一对多,多对一和多对多通信
  • UDP首部开销小:8字节
  • UDP是全双工

UDP缓存区

  • UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后

续的传输动作;

  • UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果

缓冲区满了, 再到达的UDP数据就会被丢弃,而且也不会通知你。

UDP格式

  • 源端口号(选用):在需要对方回信时选用,不需要则全0
  • 目的端口号:终点交付报文时必用
  • 长度:整个报文长度(大小), 最小为8字节
  • 校验:检查报文传输中是否损坏

image-20240508195906910

如果我们需要传输的数据超过64K, 2^16bit, 则需要手动拆解数据。

UDP的应用场景

  • NFS: 网络文件系统

  • TFTP: 简单文件传输协议

  • DHCP: 动态主机配置协议

  • BOOTP: 启动协议(用于无盘设备启动)

  • DNS: 域名解析协议

  • 直播,视频

UDP的使用

在Linux系统中,对UDP头部的定义

1
2
3
4
5
6
struct udphdr {
    __be16  source;      /* 源端口 */
    __be16  dest;        /* 目标端口 */
    __be16  len;         /* UDP数据包长度 */
    __sum16 check;       /* 校验和 */
};

对UDP的缓存区的定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct sk_buff {
    struct sk_buff         *next;       /* 下一个缓冲区 */
    struct sk_buff         *prev;       /* 上一个缓冲区 */
    struct sk_buff_head    *list;       /* 缓冲区链表 */
    struct sock            *sk;         /* 相关的套接字 */
    unsigned char          *data;       /* 数据指针 */
    unsigned int           len;         /* 数据长度 */
    unsigned int           data_len;    /* 数据部分长度 */
    unsigned int           truesize;    /* 真实大小 */
    struct udphdr          *udp;        /* UDP头指针 */
    /* 其他成员 */
};

TCP(Transmission Control Protocol)

传输控制协议

TCP的特点

  • 基于连接
  • 可靠交付(无差错,不丢失,不重复)
  • 面向字节流
  • 全双工
  • 一对一通信

TCP的格式

TCP的数据我们称为数据段(报文段)

注:同一份数据,在传输层称为数据段,网络层称为数据包,数据链路层称为数据帧

image-20240510085136785

  • 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;

  • 32位序号/32位确认号: 保障有序性

  • 4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60

  • 6位标志位:TCP报文是有类型的,比如一个TCP报文可能是通信报文,也可以是建立连接的报文,也可以是关闭连接的报文,因此需要用标志来区分。

​ URG: 紧急指针是否有效

​ ACK: 确认号是否有效,当该报文是应答类型的报文,该标志置1

​ PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走

​ RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段

​ SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段

​ FIN: 通知对方, 本端要关闭了 , 我们称携带FIN标识的为结束报文段

  • 16位窗口大小: 16位窗口大小最大为2^16-1 = 65532 字节
  • 16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.
  • 16位紧急指针: 标识哪部分数据是紧急数据。只有当URG标志为1,才有效。紧急指针的本质是紧急数据在TCP报文里的偏移量。
  • 40字节头部选项: 暂时忽略;

TCP的连接管理

TCP建立连接,需要进行3次握手和4次挥手

3次握手和4次挥手

image-20240513184431231

为什么是3次握手?1次,2次为什么不行?

3次握手的目的:确认双方的通信能力和可达性同步双方的序列号

​ 第一次:客户端发送报文给服务器,服务器接受到报文:服务器知道客户端发送能力正常

​ 第二次:服务器做出应答,客户端接受到报文:客户端知道服务器的接受发送能力正常

​ 第三次:客户端做出应答,服务器接受到报文:服务器知道客户端的接受能力正常

但是这个回答是比较片面的,RFC 793:首要原因是为了防止旧的重复连接初始化造成混乱。

例:在网络拥堵情况下,客户端连续发送多次SYN建立连接的报文,3次握手可以防止历史连接的干扰。如下图:

image-20250411143924451

2次握手为什么解决不了上面的情况,原因在于:服务端没有中间状态(SYN RCVD)给客户端来阻止历史连接。 2次握手下,服务器收到SYN,就会进入ESTABUSHED.

4次挥手能合成为3次挥手?

问题:为什么要4次挥手,第2次挥手和第3次挥手为什么不能采用捎带应答的方式合成一次?

答:服务器收到FIN后,会发送ACK,进入CLOSE_WAIT,CLOSE_WAIT会处理一些未来得及处理的数据,处理完成后,再发送FIN. 如果缓冲区本身没有数据需要处理,那么可以将第2次和第3次合为捎带应答 ==> FIN + ACK

因此4次挥手可以合成为3次挥手

TCP的可靠传输

TCP核心是可靠传输,而可靠传输体现在3个方面:无差错,无丢失,无重复

无差错:16位校验号

无丢失:自动重传机制(ARQ), 超时重传机制(Retransmission Timeout),滑动窗口

无重复:序列号、确认号、确认应答机制

下面一个一个介绍:

确认应答(ACK)机制

要想确定一个报文是否有效,需要对方做出应答。但应答的有效性无法验证(验证应答的有效性会陷入循环),因此我们规定不用对 应答 做出 应答。对方发出应答就默认我收到了应答。

捎带应答:多数情况下,单纯发出应答效率不高,而且根据规定,对方发出应答就默认我收到了应答。因此为了提高效率,对方发出的报文 = 应答 + 数据。

image-20240513144431282

序列号(Sequence number)

TCP通信时,并不是发一个,应答一个。这样效率低下。而是一次发多个数据,发送多个数据存在乱序问题,而乱序是不可靠的一种。

TCP保证有序是通过序列号来实现(TCP报文里的32位序号)。每个报文都带有序号,对方收到多个报文,会根据序号进行排序。如何理解这个序号?

  • TCP是面向字节流的,因此我们可以抽象的认为TCP的缓存区是一个 char类型的数组,数组的元素天然带有一个编号(数组下标)

  • TCP 报文头中的 32 位序号字段用于标识 TCP 连接中每个数据包的顺序。**这个序号表示了数据包中的第一个字节在整个数据流中的位置。**序号的增长是根据发送的数据量而定的,每发送一个字节,序号就增加一个。这样,接收端就可以根据序号来确定接收到的数据包的顺序,并进行重组。

  1. 如果发送方发送了第一个TCP段,包含100字节数据,且这个TCP段的序列号为1000。
  2. 接下来发送第二个TCP段时,它的序列号将是1100(1000 + 100),因为第一个段包含了100个字节。

初始的序列号如何生成?

起始ISN是基于时钟的,每4微秒+1,转一圈要4.55个小时。 RFC793提到

​ 初始化序列号ISN随机生成算法:ISN = M + F(localhost,localport,remotehost,remoteport)。

M是一个计时器,这个计时器每隔4微秒加1。

F是一个Hash 算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用MD5算法是一个比较好的选择。 可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

确认号

接受方收到一个序列号为N报文后,需要应答。该应答需要告诉对方,自己接受到了序号为N,因此要返回一个序号为M确认序号,**规定:确认序号 = 最后一个已成功接收的数据字节的序列号 + 1, 即 M = N+1 **。通知发送方可以从M处开始发数据了。

当发送方收到序号为M的应答后,规定:发送方可以认为小于序号M的所有报文,接受方全部收到。这样,我们可以允许少量应答丢失。

image-20240513152659063

那为什么一个报文同时带有确认号和序列号?确认号 – 应答, 序列号 – 数据,由于存在捎带应答的情况,一个报文即是应答又是数据,因此需要同时带上确认号和序列号。

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);

流量控制是通过接收端抑制发送端发送数据的速率,以使接收端来得及接收。是点对点通信量的控制,是个端到端的问题。

流量控制的手段:滑动窗口

image-20240515165815537
  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;

  • 窗口大小字段越大, 说明网络的吞吐量越高;

  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;

  • 发送端接受到这个窗口之后, 就会减慢自己的发送速度;

  • 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.

滑动窗口

刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.

这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候。因此为了提高效率,我们需要一次发送多段报文,而TCP使用滑动窗口来管理多段数据的发送。

窗口越大, 网络吞吐量就越大, 传输效率就越高.

问题:滑动窗口在哪里?发送缓存区的一部分

滑动窗口将发送缓存区分为了3部分:

img

具体如下:

image-20240515155939215

滑动窗口如何解决丢包问题?

  1. ACK丢了
image-20240515163846423

根据确认序号的定义:该序号之前的数据已经全部接受。

因此少量的丢包不会影响。

  1. 数据包丢了

    image-20240515164254088

现在:发送方:1000 2000 3000 4000 其中2000丢了 接受方没有接受到2000,但接受到了3000 4000,但根据确认序列号的定义,接受方只能发送ACK = 1001

拥塞控制

虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题. 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的. 因此TCP引入了拥塞控制

拥塞控制的目的是防止过多的数据注入到网络中,避免网络中的路由器或链路过载。是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。

image-20240515170711380

TCP 进行拥塞控制的算法有四种

  • 慢开始(slow-start)
  • 拥塞避免(congestion avoidance)
  • 快重传(fast retransmit)
  • 快恢复(fast recovery)

此外引入2个概念:拥塞窗口(cwnd:congestion window)和慢开始门限 (ssthresh)

拥塞窗口:初始为1,根据拥塞控制算法进行修改,它的作用:16位窗口大小 = min(cwnd, rwnd)

慢开始门限:为了防止拥塞窗口 cwnd 增长过大引起网络拥塞,还需要设置一个慢开始门限。具体来说,它用来控制什么情况下执行哪一种拥塞控制算法。

算法思想如下:cwnd 初始置1

  1. 刚开始执行慢开始算法,cwnd成指数增长

  2. 当cwnd > ssthresh,执行拥塞避免算法(特殊情况:cwnd == ssthresh,执行慢开始、拥塞避免都可以),开始进行线性增长

  3. 当出现下面2种情况:

    • 情况1:超时,发送方长时间未收到接受方的ACK,判定为网络拥塞。则cwnd置1,ssthresh变为超时时的cwnd的一半。进入慢开始阶段

      image-20240618144123833
    • 情况2:快重传,发送方收到接受方3个相同的ACK。则cwnd == ssthresh == 快重传时cwnd的一半,进入快恢复阶段

      image-20240618144258465

理解:为什么分2种情况?

答:有时,个别报文段会在网络中意外丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,并误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口 cwnd 又设置为 1,因而不必要地降低了传输效率。因而引入快重传算法

快重传机制

快重传算法规定,发送力只要一连收到3个重复确认,就可知道现在并未出现网络拥塞,而只是接收方少收到一个报文段 M3 ,因而立即进行重传M3 (即“快重传”)。使用快重传可以使整个网络的吞吐坟提高约 20%.

image-20240618151505806

超时重传机制

主机A发送给主机B的数据可能因为网络问题而丢失,或者主机B由于某些原因,收到了报文,但没有应答。

如果主机A在一定时间内没有收到主机B的应答,则会重发。

问题:

  1. 主机B可能收到多个相同的报文,如何去重? 根据序列号

  2. “超时时间”是如何规定的?当网络良好时,超时时间太长,会导致效率低下,同理当网络不好时,超时时间太小,也会导致效率低下。

    Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时

    时间都是500ms的整数倍.

    如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.

    如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.

    累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.

image-20240513141329940

TCP异常情况分析

SYN洪水

.

Licensed under CC BY-NC-SA 4.0
本站所有文章均为原创,转载请注明出处。
使用 Hugo 构建
主题 StackJimmy 设计