什么是TCP滑动窗口?(解释+流程示例)

本文详细解释了滑动窗口在TCP协议中的工作原理,包括其在流量控制和拥塞控制中的作用,以及发送窗口和接收窗口的结构和操作。通过滑动窗口,接收方动态调整窗口大小以控制发送速率,确保数据传输的高效和可靠。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、滑动窗口机制

TCP 发送一个数据,如果需要收到确认应答,才会发送下一个数据。这样的话就会有个缺点:效率会比较低。

“用一个比喻,我们在微信上聊天,你打完一句话,我回复一句之后,你才能打下一句。假如我没有及时回复呢?你是把话憋着不说吗?然后傻傻等到我回复之后再接着发下一句?”

为了解决这个问题,TCP 引入了窗口,它是操作系统开辟的一个缓存空间。窗口大小值表示无需等待确认应答,而可以继续发送数据的最大值。

TCP 头部有个字段叫 win,也即那个 16 位的窗口大小,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。

通俗点讲,就是接受方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的缓存区还有多少空余空间,缓冲区的空余空间,我们就称之为接受窗口大小——这就是 win。

1.1 什么是滑动窗口

定义:滑动窗口是TCP协议中的一个重要机制,用于控制、管理发送方和接收方之间的数据传输。是TCP实现流量控制和拥塞控制的基础。

作用:滑动窗口机制允许发送方和接收方之间实现流量控制和可靠性传输发送方可以持续发送数据而不需要等待每个数据段的确认,从而提高传输效率。接收方可以根据自身的处理能力和缓冲区空间调整窗口大小,从而控制发送方窗口大小。

“用接收窗口大小控制发送窗口大小,再由发送窗口大小控制发送速率。”

滑动窗口组成:

在TCP中,发送方维护一个发送窗口swnd,接收方则会维护一个接收窗口rwnd,它们是一个连续的字节序列,表示发送方可以发送的数据范围大小。窗口由两个参数定义:窗口的起始字节和窗口的大小。

发送方将数据分成多个数据段,按顺序发送到接收方。每个数据段都包含一个序列号,标识数据在发送方发送窗口中的位置。接收方使用ack确认应答报文来通知发送方已成功接收数据 ,随后发送方通过ack报文窗口滑动。

ack字段值表示接收方期望接收的下一个字节的序列号

滑动窗口机制是实现流量控制和拥塞控制的重要机制,具体详情请看我这几篇文章:

什么是TCP流量控制?(解释说明+过程+示例)-CSDN博客

什么是TCP的拥塞控制(解释+常见算法)-CSDN博客

1.2 滑动窗口的工作原理

  1. 协商初始化窗口大小。在建立TCP连接时,双方协商并初始化流量控制的参数。其中包括窗口大小(通常是以字节为单位的接收缓冲区大小)和初始的拥塞窗口cwnd大小(swnd=min(cwnd, rwnd))。
  2. 发送窗口滑动:发送方发送一个数据段并收到ACK确认应答后,将发送窗口向前滑动,使其离开已确认的数据。这样,发送方可以继续发送新的数据,只要它在滑动窗口范围内。
  3. 接收方更新确认号:接收方根据接收到的报文段的序列号确定已成功接收的数据字节范围,并将确认号设置为下一个期望接收的字节的序列号(通常为接受到的报文段下一位)。
  4. 接收方更新、通告接收窗口大小:接收方根据已成功接收的数据字节数和初始窗口大小计算可用的接收窗口大小。并接收方将新的接收窗口大小通过 TCP 报文段中的窗口大小字段通告给发送方。这个值告诉发送方接收方的当前可用缓冲区空间。
  5. 动态调整窗口大小:接收方通过ACK确认号通知发送方已成功接收的数据。发送方可以根据接收方通告的窗口大小进行数据发送控制——如果接收方的窗口变大,发送方可以发送更多的数据;如果接收方的窗口变小,发送方需要适应减少的窗口大小。
  6. 流量控制:通过滑动窗口机制,接收方可以动态调整窗口大小以限制发送方的数据发送速率。接收方通过通告窗口大小,告知发送方自己的可用缓冲区空间。发送方根据接收方的窗口大小调整发送速率,确保不会超出接收方的处理能力。

1.3 发送窗口和接受窗口

TCP 滑动窗口分为两种: 发送窗口和接收窗口。

发送端的滑动窗口包含四大部分,如下:

  • 已发送且已收到 ACK 确认

  • 已发送但未收到 ACK 确认

  • 未发送但可以发送

  • 未发送也不可以发送

发送端滑动窗口

编辑

发送端滑动窗口

  • 深蓝色框里就是发送窗口。

  • SND.WND: 表示发送窗口的大小, 上图虚线框的格子数是 10 个,即发送窗口大小是 10。

  • SND.NXT:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。

  • SND.UNA: 一个绝对指针,它指向的是已发送但未确认的第一个字节的序列号。

接收方的滑动窗口包含三大部分,如下:

  • 已成功接收并确认

  • 未收到数据但可以接收

  • 未收到数据并不可以接收的数据

接收方滑动窗口

接收方滑动窗口

  • 蓝色框内,就是接收窗口。

  • REV.WND: 表示接收窗口的大小, 上图虚线框的格子就是 9 个。

  • REV.NXT: 下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。

参考来源:二哥的进阶之路

参考链接:

计算机网络面试题,62道计算机网络八股文(2.2万字80张手绘图),面渣逆袭必看👍 | 二哥的Java进阶之路

二、滑动窗口示例

综上,举个发生流量控制和超时重传的滑动窗口例子,假设发送方需要发送的数据总长度为 400 字节,分成 4 个报文段,每个报文段长度是 100 字节: 1)

  1. TCP三次握手连接建立时,接收方告诉发送方:我的接收窗口rwnd大小是 300 字节。
  2. 发送方发送第一个报文段(序号 1 - 100),还能再发送 200 个字节。
  3. 发送方发送第二个报文段(序号 101 - 200),还能再发送 100 个字节。
  4. 发送方发送第三个报文段(序号 201 - 300),还能再发送 0 个字节 此时,发送方的窗口中存了三个待收到ACK的报文段了。
     
  5. 接收方接收到了第一个报文段: ack = 101, rwnd = 200,第三个报文段:ack = 101, rwnd = 200,中间第二个报文段丢失。(同时假设这里发生流量控制,把窗口大小降到了 200。原本rwnd=300-100+100,rwnd收到一个报文段-100,处理一个报文段+100,随后流量控制窗接收方缓冲区还剩200)。

    此时的接收方滑动窗口右端正常来说应该右移一个,但是这里发生了流量控制,接收方希望缩小窗口大小,所以正好,这里就不需要向右扩展了:
  6. 发送方收到第一个包和第三个包的确认,并从窗口中删除第一个包段。但swnd的左端滑动到101的时候,还未收到第二个包的确认,无法继续滑动,然后等待收到该报文。
        其中,每次swnd窗口都会根据ack报文进行调整、滑动(接收窗口rwnd大小变为200, 那发送方swnd也得变小)。此时的发送方滑动窗口swnd如下:
  7. 发送端没有收到第二个报文段的确认回复,等待超时后重新发送第二个报文段(序号 101 - 200),并且启动TCP拥塞控制(但在这里被省略了,只专注于窗口滑动)。
     
  8. 接收端成功接收到第二个包段(之前收到了第一、三个报文段),并返回一个报文段 ack = 301, rwnd = 100 给发送端(假设这里发生了流量控制,接收方将窗口大小减少到100)。
  9. 发送方窗口收到之前所有ACK,发送窗口根据最后那个ACK报文ack=301,rwnd=100进行调整,使得swnd=100,swnd左端向右滑动到ACK期待下一个位置301。
  10. 发送方发送第四个报文段(序号 301 - 400)。

示例参考来源:力扣(LeetCode)

参考链接:力扣

总结

滑动窗口(Sliding Window)是一种用于实现流量控制和可靠数据传输的机制,在网络通信中被广泛使用,尤其是在传输控制协议(TCP)中。

滑动窗口是发送方和接收方之间的一种协议约定,用于控制发送方发送数据的速率和接收方接收数据的能力。接收方按照自己接收缓冲区大小和处理能力决定接收窗口大小,用接收窗口大小控制发送窗口大小,再由发送窗口大小控制发送速率。

TCP 滑动窗口协议是一种流量控制协议,用于在网络上传输数据的过程中控制数据包的发送和接收。在 C++ 中,可以通过 socket 编程来实现 TCP 滑动窗口协议。 以下是一个简单的示例代码演示如何使用 C++ 实现 TCP 滑动窗口协议: ``` #include <iostream> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <cstring> int main(int argc, char *argv[]) { const char *server_ip = "127.0.0.1"; const int server_port = 8000; int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << "failed to create socket" << std::endl; return -1; } sockaddr_in server_addr{}; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(server_port); inet_pton(AF_INET, server_ip, &server_addr.sin_addr); if (connect(sockfd, (sockaddr *) &server_addr, sizeof(server_addr)) < 0) { std::cerr << "failed to connect to server" << std::endl; return -1; } const char *msg = "hello world"; size_t msg_len = strlen(msg); const int window_size = 4; int window_start = 0; int window_end = window_start + window_size; while (window_start < msg_len) { for (int i = window_start; i < window_end && i < msg_len; i++) { send(sockfd, &msg[i], 1, 0); } char ack; recv(sockfd, &ack, 1, 0); if (ack == '1') { window_start += 1; window_end += 1; } } close(sockfd); return 0; } ``` 在这个示例代码中,我们先创建了一个 TCP socket,并通过 connect 函数连接到指定的服务器。然后我们定义了一个消息 msg,以及一个窗口大小 window_size,初始窗口起始位置为 window_start,窗口结束位置为 window_end。 在 while 循环中,我们不断向服务器发送数据,每次发送窗口范围内的数据。一旦收到服务器的响应,我们就将窗口滑动一个位置,继续发送下一批数据。 需要注意的是,这个示例代码并没有考虑丢包和重传的情况,实际上在实际应用中需要对 TCP 滑动窗口协议进行更加复杂的处理。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值