socket编程UDP-实现滑动窗口机制与累积确认GBN

news2024/12/19 4:32:20

在下面博客中,我介绍了利用UDP模拟TCP连接、按数据包发送文件的过程,并附上完整源码

socket编程UDP-文件传输&模拟TCP建立连接脱离连接(进阶篇)_udp socket发送-CSDN博客

下面博客实现了停等机制。

socket编程UDP-实现停等机制(接收确认、超时重传)-CSDN博客

本篇博客,我将在此基础上实现滑动窗口机制,完成客户端发送服务器接收的累积确认(GBN)

目录

一、协议设计

1.思路概览

2.完整实例 

二、核心代码

1.代码实现思路

 2. 核心源码

3.可运行完整源码

三、运行演示

1.正常传输(收到正确序列号)

2.乱序抵达(出现丢包情况,收到偏大/偏小数据包)


一、协议设计

1.思路概览

​ 1)对于发送端(客户端)发送窗口为N>1,每次发送的基准窗口设置为base(即每轮发送的最小序列值)。并且:

  • 允许发出N个未得到确认的分组,连续发送而不等待接收端的回复

  • 如果在定时范围内收到该轮最大的ack(expectedAck=base+N-1),立即更新基准窗口base=expectedAck+1,并且开始下一轮发送。(也就是说,即使定时器时间未到,也立刻开始下一轮发送)

  • 如果在定时范围内没有收到该轮最大的ack,将base更新为这段时间收到的最大ack值+1,即base=max(getAck)+1.并且从base开始进行下一轮发送

2)对于接收端(服务器端)接收窗口大小设置为1,因此每收到一个数据包,都会发送一次ack。为了处理丢包,设置buffer缓冲区,用于存储乱序抵达的包

  •  如果收到期待的数据包(seq==expectedseq,其中expectedseq=last\_ack+1),将数据写入文件,并检查缓冲区中是否存储有后续数据包的内容;如果有,将缓冲区的内容写入文件。依据已经写入文件的数据包序列号来更新expectedseq,并发送ack = expectedSeq - 1。

  •  如果收到的数据包序列号大于期待值(seq>expectedseq),将数据暂存进缓冲区buffer,发送最后一次按序抵达的数据包对应的ack.

  • 如果收到的数据包序列号小于期待值(seq<expectedseq),说明收到了重复的数据包,,只发送最后一次按序抵达的数据包对应的ack

2.完整实例 

 

  • 发送窗口大小设置为4,初始时客户端连续发送序列号为0,1,2,3的四个数据包。

  • 图中的seq=0,seq=1的数据包按序抵达(seq==expectedseq,其中expectedseq=last\_ack+1),服务器端会发送对应的ack=0,ack=1.

  •  图中seq=2的数据包丢失,于是当seq=3的数据包抵达时,服务器端收到了偏大的序号(seq=3>expectedseq=2)。服务器端会暂存seq=3的数据包到缓冲区中,并发送已经收到的最大正确序号(ack=1).

  • 客户端一直期待收获到“ack=3”,等待一段时间没有收到,说明超时。于是基准窗口移动为收到的最大ack+1,即1+1=2,发送序列号为2,3,4,5的四个数据包。

  •  服务器端收到seq=2的数据包后,由于之前暂存了seq=3的数据包,因此ack更新为3,将两个数据包一起写入文件,并发送ack=3.

  • 服务器端再次收到seq=3的数据包,不会重复写入,依旧发送ack=3.

  •  服务器端成功接收四个数据包,客户端收到期待的ack(ack=5),不再等待定时器到时,立刻更新基准窗口为6,开始下一轮发送。

二、核心代码

1.代码实现思路

服务器端为接收方,只需要简单地接收数据包并发送对应ack.

而客户端的实现较为复杂,主要逻辑如下图:

客户端每收到一个ack,就会比较ack与getACK的值,把最大ack赋给getACK.

如果客户端收到了期待的最大ack(即传输过程中没有出现丢包),会立即更新base并开始下一轮发送;

如果客户端未收到期待的最大ack,会陷入等待,定时器到时后会将base更新为getAck+1,已经读取过的数据包重新发送、未读取的数据包读取后再发送。 

 2. 核心源码

 bool Sender::waitForAck() {
    std::unique_lock<std::mutex> lock(mtx);
    //如果 `ackReceived` 在超时之前变为 `true`,则 `wait\_for` 返回并继续执行后续代码;
    //如果超时后 `ackReceived` 仍为 `false`,则 `wait\_for` 也会返回。
    return cv.wait_for(lock, std::chrono::milliseconds(5*TIMEOUT), [this]() { return ackReceived.load() ; });//超时或者收到所有ack返回
}
 void Sender::receiveAck() {
    Datagram ackPacket(SERVER_PORT,ROUTER_PORT);
    socklen_t len = sizeof(routerAddr);
    while (true) {
        if (recvfrom(sock, reinterpret_cast<char*>(&ackPacket), sizeof(ackPacket), 0, (struct sockaddr*)&routerAddr, &len) > 0) {
            if (ackPacket.flag == 0 && ackPacket.validateChecksum(clientAddr.sin_addr.S_un.S_addr, routerAddr.sin_addr.S_un.S_addr)) {
                if(ackPacket.ack==65535)
                {
                    continue;
                }
                std::lock_guard<std::mutex> lock(mtx);
                std::cout << "收到ack=" << ackPacket.ack << std::endl;
                if(ackPacket.ack>getAck){getAck=ackPacket.ack;}
                if(ackPacket.ack==expectedAck)
                {
                    ackReceived = true;
                    cv.notify_one();
                }   
            }
        }
    }
}

void Sender::sendFile(const std::string& filename) {
      //.......
        base = 0;
        nextseq = 0;
        while (true) {
            //.......
            //1.基于窗口连续发送N条消息
            while (nextseq < base + WINDOW_SIZE && !file.eof()) {
                if(nextseq<3&&AckPacket.flag == 2&&AckPacket.validateChecksum(clientAddr.sin_addr.S_un.S_addr, routerAddr.sin_addr.S_un.S_addr))//2.如果此时又收到了SYN-ACK
                {
                   //...重新发送
                }
                std::unique_lock<std::mutex> lock(mtx);
                ackReceived = false;
                expectedAck = base+WINDOW_SIZE-1;
                Datagram packet;//默认构造,从客户端发往路由器端
                packet.seq = nextseq;
                file.read(packet.data, BUFFER_SIZE);
                packet.dataSize = static_cast<int>(file.gcount());
                packet.flag = 0;

                window[nextseq] = packet;
                //window.emplace(nextseq, packet);
                sendPacket(packet);
                std::cout << "发送数据包.SEQ=" << packet.seq <<",校验码="<< packet.checksum << std::endl;
                nextseq++;
                lock.unlock();
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT));
            
            // 2.结束条件:如果所有包都被确认,则跳出
            if (base == nextseq && file.eof()) break;
            if (waitForAck()) {
                std::unique_lock<std::mutex> lock(mtx);
                base = base + WINDOW_SIZE;
                std::cout << "base窗口后移为:" << base << ",开始下一轮发送" << std::endl;
            } else {
                std::unique_lock<std::mutex> lock(mtx);
                if(getAck==-1){base=0;}
                else{base =getAck+1;}
                std::cout << "未收到所有ack,base窗口后移为:" << base << ",重新发送此部分" << std::endl;
                for (int i = base; i < nextseq; ++i) {
                    sendPacket(window[i]);
                    std::cout << "发送数据包.SEQ=" << window[i].seq << ",校验码=" << window[i].checksum << std::endl;
                }
            }
        }
        //.......
    }

3.可运行完整源码

已上传github:

https://github.com/yeyeyeyeye-zhang/Computer-Network/tree/main/lab3-2/codes

三、运行演示

在src目录下输入:

 g++ -o cs main.cpp Datagram.cpp Sender.cpp Receiver.cpp -lws2_32

1.正常传输(收到正确序列号)

客户端发送数据包,收到期待ack值,窗口正常后移的情况

服务器端收到期待序列号的数据包,正常发送对应ack的情况

2.乱序抵达(出现丢包情况,收到偏大/偏小数据包)

客户端发送时出现丢包现象,丢失seq=493的数据包,收到的ack值为492.定时器超时后,base窗口后移为493,发送从493到496的数据包。

 服务器端未收到seq=493的数据包,之后再收到seq=494,495的数据包都仅发送ack=492.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2261934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android-Glide详解

目录 一&#xff0c;介绍 二&#xff0c;使用 三&#xff0c;源码分析思路 四&#xff0c;with源码分析 五&#xff0c;模拟Glide生命周期管理 一&#xff0c;介绍 Glide目前是安卓最主流的加载图片的框架&#xff0c;也是源码最为复杂的框架之一。 要想完完全全吃透Glide的源…

服务器Ubuntu22.04系统下 ollama的详细部署安装和搭配open_webui使用

服务器Ubuntu22.04系统下 ollama的详细部署安装和搭配open_webui使用 一、ubuntu和docker基本环境配置 1.更新包列表&#xff1a;2. 安装docker依赖3. 添加docker密钥4.添加阿里云docker软件源5.安装docker6.安装完成docker测试7. docker配置国内镜像源 二、安装英伟达显卡…

【视频生成模型】——Hunyuan-video 论文及代码讲解和实操

&#x1f52e;混元文生视频官网 | &#x1f31f;Github代码仓库 | &#x1f3ac; Demo 体验 | &#x1f4dd;技术报告 | &#x1f60d;Hugging Face 文章目录 论文详解基础介绍数据预处理 &#xff08;Data Pre-processing&#xff09;数据过滤 (Data Filtering)数据标注 (Data…

Node的学习以及学习通过Node书写接口并简单操作数据库

Node的学习 Node的基础上述是关于Node的一些基础&#xff0c;总结的还行&#xff1b; 利用Node书写接口并操作数据库 1. 初始化项目 创建新的项目文件夹&#xff0c;并初始化 package.json mkdir my-backend cd my-backend npm init -y2. 安装必要的依赖 安装Express.js&…

git diff 查看差异

一.查看工作区和暂存区之间文件的差异 git diff 命令&#xff0c;默认查看的就是 工作区 和 暂存区之间文件的差异 1.git diff : 查看工作区和暂存区之间所有的文件差异 2.git diff -- 文件名&#xff1a;查看具体某个文件 在工作区和暂存区之间的差异 3.git diff -- 文件名…

linux网络编程 | c | epoll实现IO多路转接服务器

epoll实现IO多路转接服务器 可通过以下视频学习 06-opell函数实现的多路IO转接_哔哩哔哩_bilibili 通过响应式–多路IO转接实现 文章目录 epoll实现IO多路转接服务器1.思路&功能核心思路 2.代码实现multi_epoll_sever.c运行图 1.思路&功能 **功能&#xff1a;**客…

Java全体系精华(上):从基础到框架,构建坚实开发技能

爱的故事.上集 1. Java 基础1.1 常用集合数据结构 Array List Map Set Tree1.1.1 常用集合在JDK中的结构1.1.2 List 底层是数组1.1.3 Map键值对结存储结构1.1.3.1 为什么HashMap的Key、Value都允许为 null1.1.3.2 为什么ConcurrentHashMap的Key、Value都不允许为null1.1.3.3 Ha…

基于Clinical BERT的医疗知识图谱自动化构建方法,双层对比框架

基于Clinical BERT的医疗知识图谱自动化构建方法&#xff0c;双层对比框架 论文大纲理解1. 确认目标2. 目标-手段分析3. 实现步骤4. 金手指分析 全流程核心模式核心模式提取压缩后的系统描述核心创新点 数据分析第一步&#xff1a;数据收集第二步&#xff1a;规律挖掘第三步&am…

ctfshow--web入门之爆破篇

知识点&#xff1a; 暴力破解原理 暴力破解实际就是疯狂的输入密码进行尝试登录&#xff0c;针对有的人喜欢用一些个人信息当做密码&#xff0c;有的人喜欢用一些很简单的低强度密码&#xff0c;我们就可以针对性的生成一个字典&#xff0c;用脚本或者工具挨个去尝试登录。 …

Web项目图片视频加载缓慢/首屏加载白屏

Web项目图片视频加载缓慢/首屏加载白屏 文章目录 Web项目图片视频加载缓慢/首屏加载白屏一、原因二、 解决方案2.1、 图片和视频的优化2.1.1、压缩图片或视频2.1.2、 选择合适的图片或视频格式2.1.3、 使用图片或视频 CDN 加速2.1.4、Nginx中开启gzip 三、压缩工具推荐 一、原因…

Go 语言与时间拳击理论下的结对编程:开启高效研发编程之旅

一、引言 结对编程作为一种软件开发方法&#xff0c;在提高代码质量、增强团队协作等方面具有显著优势。而时间拳击理论为结对编程带来了新的思考角度。本文将以 Go 语言为中心&#xff0c;深入探讨时间拳击理论下的结对编程。 在当今软件开发领域&#xff0c;高效的开发方法和…

SpringBoot集成ENC对配置文件进行加密

在线MD5生成工具 配置文件加密&#xff0c;集成ENC 引入POM依赖 <!-- ENC配置文件加密 --><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.2</ver…

el-table 多表头+跨行跨列案例

效果&#xff1a; 代码&#xff1a; index.vue <template><div class"my-table"><el-tablev-loading"table.loading":data"table.data"bordersize"mini":header-cell-style"headerCellStyle":span-method&qu…

基线检查:Windows安全基线.【手动 || 自动】

基线定义 基线通常指配置和管理系统的详细描述&#xff0c;或者说是最低的安全要求&#xff0c;它包括服务和应用程序设置、操作系统组件的配置、权限和权利分配、管理规则等。 基线检查内容 主要包括账号配置安全、口令配置安全、授权配置、日志配置、IP通信配置等方面内容&…

【electron】electron forge + vite + vue + electron-release-server 自动更新客户端

基本信息 electron forge vue页面&#xff08;中文&#xff09;&#xff1a;https://forge.electron.js.cn/guides/framework-integration/vue-3 electron forge vue页面&#xff08;英文&#xff0c;中文版下面的tab无法点击&#xff09;&#xff1a;https://www.electronfor…

ubuntu+ros新手笔记(二):古月·ROS2入门21讲学习笔记

系统ubuntu22.04 ros2 humble 按照如下视频教程学习的&#xff1a;【古月居】古月ROS2入门21讲 | 带你认识一个全新的机器人操作系统 此处仅记录我报错的地方&#xff0c;以及相应的解决方案&#xff0c;没有出错的略过&#xff01; 对应的古月居ROS2入门21讲源码下载地址&a…

IDEA 可视化使用 git rebase 合并分支步骤 使git分支树保持整洁

模拟环境 dev 分支开发完一个功能&#xff0c;需要合并到 master 分支&#xff0c;如果现在直接 merge 合并的话 git分支树会出现杂乱分叉&#xff0c;先把 master 分支 rebase 到 dev git分支树就会是整洁的一条直线 git rebase介绍 rebase:翻译成中文是重新设定&#xff0c;…

Windows环境 (Ubuntu 24.04.1 LTS ) 国内镜像,用apt-get命令安装RabbitMQ,java代码样例

一、环境 Windows11 WSL(Ubuntu 24.04.1) 二、思路 1 用Windows中的Ubuntu安装RabbitMQ&#xff0c;贴近Linux的线上环境&#xff1b; 2 RabbitMQ用erlang语言编写的&#xff0c;先安装erlang的运行环境&#xff1b; 2 用Linux的apt-get命令安装&#xff0c;解决软件依赖…

使用ElasticSearch实现全文检索

文章目录 全文检索任务描述技术难点任务目标实现过程1. java读取Json文件&#xff0c;并导入MySQL数据库中2. 利用Logstah完成MySQL到ES的数据同步3. 开始编写功能接口3.1 全文检索接口3.2 查询详情 4. 前端调用 全文检索 任务描述 在获取到数据之后如何在ES中进行数据建模&a…

CTFHUB-web(SSRF)

内网访问 点击进入环境&#xff0c;输入 http://127.0.0.1/flag.php 伪协议读取文件 /?urlfile:///var/www/html/flag.php 右击查看页面源代码 端口扫描 1.根据题目提示我们知道端口号在8000-9000之间,使用bp抓包并进行爆破 POST请求 点击环境&#xff0c;访问flag.php 查看页…