Java网络编程,使用UDP实现TCP(三), 基本实现四次挥手

简介

四次挥手示意图

  • 在四次挥手过程中,第一次挥手中的Seq为本次挥手的ISN, ACK为 上一次挥手的 Seq+1,即最后一次数据传输的Seq+1。
  • 挥手信息由客户端首先发起。

实现步骤:

下面是TCP四次挥手的步骤:

  1. 第一次挥手(FIN):主动关闭方发送一个带有FIN(Finish)标志的TCP报文段给被动关闭方,表示主动关闭方已经没有数据要发送了。

  2. 第二次挥手(ACK):被动关闭方接收到第一次挥手的TCP报文段后,发送一个带有ACK(Acknowledgment)和确认序号的TCP报文段作为响应,表示已经收到了关闭请求。

  3. 第三次挥手(FIN):被动关闭方发送一个带有FIN标志的TCP报文段给主动关闭方,表示被动关闭方也没有数据要发送了。

  4. 第四次挥手(ACK):主动关闭方接收到第三次挥手的TCP报文段后,发送一个带有ACK和确认序号的TCP报文段作为响应,表示已经收到了关闭请求。

在完成这四次挥手之后,TCP连接就正式关闭了。需要注意的是,每一次挥手都需要对方的确认才能进行下一步操作,这样可以确保双方都知道连接已经关闭,并且保证数据的完整性和可靠性。

这个过程可以简化为以下步骤:

  1. 主动关闭方发送FIN报文段。
  2. 被动关闭方接收到FIN后,发送ACK报文段作为确认。
  3. 被动关闭方发送FIN报文段。
  4. 主动关闭方接收到FIN后,发送ACK报文段作为确认。

这样,TCP连接就完成了关闭过程。

修改说明:

我将客户端的发送消息和服务端的接收消息做了一些简单的封装:

客户端:

    public void sendMsg(String dataMsg, DatagramSocket datagramSocket) throws IOException {
        byte[] bytes = dataMsg.getBytes();
        DatagramPacket datagramPacketMsg = new DatagramPacket(bytes, 0,bytes.length, new InetSocketAddress("localhost",9999));
        datagramSocket.send(datagramPacketMsg);
    }

服务端:

//接收数据
    public String receive(DatagramSocket datagramSocket, int time1, int time2) throws IOException {
        //设置超时时间
        datagramSocket.setSoTimeout(time1);
        //创建数据包,用于接收数据
        byte[] bytes3 = new byte[1024];
        DatagramPacket datagramPacket3 = new DatagramPacket(bytes3, bytes3.length);

        datagramSocket.receive(datagramPacket3);
        //停止计时器
        datagramSocket.setSoTimeout(time2);
        String s3 = new String(datagramPacket3.getData(), 0, datagramPacket3.getLength());
        return s3;
    }

 

第一次挥手

客户端发送关闭请求:

 //四次挥手,关闭连接
            System.out.println("====================");
            System.out.println("四次挥手:");

            System.out.println("第一次挥手: 客户端 -> 服务端");
            System.out.println("数据发送...");
            connectionMarks.setFinMark(2);
            String finMark = String.valueOf(connectionMarks.getFinMark());
            connectionMarks.setACKMark(1);
            String ACKFin = String.valueOf(connectionMarks.getACKMark());
            String SeqFin = String.valueOf(connectionMarks.getSeq());
            String ACKS1 = String.valueOf(Integer.parseInt(SeqD1) + 1);
            String dataF1 = finMark + "/" + ACKFin + " " + SeqFin + " " + ACKS1;
            clientMsg.sendMsg(dataF1, datagramSocket);

服务端接收数据:

 //四次握手
            //第一次
            System.out.println("====================");
            String receiveB1 = serverMsg.receive(datagramSocket, 0, 0);
            System.out.println("接收到的数据段为:" + receiveB1);

            String[] s1 = receiveB1.split(" ");
            String[] splitS1 = s1[0].split("/");
            if (
                    !(splitS1[0].equals("2")
                            || splitS1[1].equals("1")
                            || s1[2].equals(String.valueOf(Integer.parseInt(SeqD1) + 1)))
            ){
                throw new WrongConnectionException("非本次连接");
            }

第二次挥手

服务端发送第一次挥手的ACK

//第二次
            System.out.println("====================");
            System.out.println("服务端 -> 客户端");
            System.out.println("数据发送...");
            String SeqB2 = s1[2];
            String ACKB2 = String.valueOf(Integer.parseInt(s1[1]) + 1);
            connectionMarks.setACKMark(1);
            String ackMarkB = String.valueOf(connectionMarks.getACKMark());
            String dataMsgB2 = ackMarkB+ " " + SeqB2 + " " + ACKB2;
            byte[] datasB2 = dataMsgB2.getBytes();
            DatagramPacket datagramPacketB2 = new DatagramPacket(datasB2, 0,datasB2.length, new InetSocketAddress("localhost",8888));

            //调用对象发送数据
            datagramSocket.send(datagramPacketB2);

客户端接收

 System.out.println("====================");
            System.out.println("开始接收数据段...");
            byte[] bytesB2 = new byte[1024];
            DatagramPacket datagramPacketB2 = new DatagramPacket(bytesB2, bytesB2.length);
            datagramSocket.receive(datagramPacketB2);
            String receiveMsgB2 = new String(datagramPacketB2.getData(), 0, datagramPacketB2.getLength());
            System.out.println("接收到的数据段为:" + receiveMsgB2);

 

第三次挥手

服务端发送请求关闭给客户端

System.out.println("====================");
            System.out.println("服务端 -> 客户端");
            System.out.println("数据发送...");

            String SeqB3 = SeqB2;
            String FinMark = splitS1[0];
            String ACKB3 = ACKB2;
            String dataMsgB3 = FinMark + "/" + ackMarkB+ " " + SeqB3 + " " + ACKB3;
            byte[] datasB3 = dataMsgB3.getBytes();
            DatagramPacket datagramPacketB3 = new DatagramPacket(datasB3, 0,datasB3.length, new InetSocketAddress("localhost",8888));

            //调用对象发送数据
            datagramSocket.send(datagramPacketB3);

 客户端接收数据,需要校验,如果收到为关闭请求,则发送ACK给服务端

 System.out.println("====================");
            System.out.println("开始接收数据段...");
            byte[] bytesB3 = new byte[1024];
            DatagramPacket datagramPacketB3 = new DatagramPacket(bytesB3, bytesB3.length);
            datagramSocket.receive(datagramPacketB3);
            String receiveMsgB3 = new String(datagramPacketB3.getData(), 0, datagramPacketB3.getLength());
            System.out.println("接收到的数据段为:" + receiveMsgB3);
            String[] splitB3 = receiveMsgB3.split(" ");
            String[] split2 = splitB3[0].split("/");
            if (
                    !(split2[0].equals("2")
                            || split2[1].equals("1")
                            ||splitB3[1].equals(ACKS1)
                            ||splitB3[2].equals(String.valueOf(Integer.parseInt(SeqFin) + 1)))
            ){
                throw new WrongConnectionException("非本次连接");
            }

第四次挥手

客户端接收并发送第三次挥手的ACK给服务端

System.out.println("====================");
            System.out.println("第四次挥手: 客户端 -> 服务端");
            System.out.println("数据发送...");
            String ackMark4 = ACKFin;
            String SeqB4 = SeqFin;
            String ACKB4 = String.valueOf(Integer.parseInt(ACKS1) + 1);
            String dataB4 = ackMark4 + " " + SeqB4 + " " + ACKB4;
            clientMsg.sendMsg(dataB4, datagramSocket);

            //关闭流
            datagramSocket.close();

客户端接收到ACK并且关闭 

System.out.println("====================");
            String receiveB4 = serverMsg.receive(datagramSocket, 0, 0);
            System.out.println("接收到的数据段为:" + receiveB4);

            //关闭流
            datagramSocket.close();

完成总结:

  • 基本完成了UDP实现TCP,但仍有欠缺。
  • 实现过程中代码的复用过高,没有进行有效的方法封装。
  • 代码不够成熟,还由一些不完善的地方,没有实现MTU机制。
  • 对UDP和TCP,有了更深入的了解。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图论——二分图

图论——二分图 二分图通俗解释 有一个图,将顶点分成两类,边只存在不同类顶点之间,同类顶点之间设有边。称图 G 为二部图,或称二分图,也称欧图。 性质 二分图不含有奇数环图中没有奇数环,一定可以转换为二…

【ET8】2.ET8入门-ET框架解析

菜单栏相关:ENABLE_DLL选项 ET->ChangeDefine->ADD_ENABLE_DLL/REMOVE_ENABLE_DLL 一般在开发阶段使用Editor时需要关闭ENABLE_DLL选项。该选项关闭时,修改脚本之后,会直接重新编译所有的代码,Editor在运行时会直接使用最…

c#_sqlserver_三层架构winform学生信息管理及选课系统

基本功能包括管理员登录、注册学生账号、删除学生信息、查找学生信息、发布课程、修改课程、删除课程等。 教师端 登录:管理员登陆,拥有相应账号即可登录(后台注册)。注册学生账号:管理员可给学生分配学号&#xff0…

HTML中常用表单元素使用(详解!)

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍HTML中常用表单元素使用以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 …

基于Maven构建OSGI应用(Maven和OSGI结合)

基于Maven构建OSGI应用。 使用Maven来构建项目,包括项目的创建、子模块buldle的创建等。使用OSGI来实现动态模块化管理,实现模块的热插拔效果(即插即用)。 创建一个Maven项目:helloworld,并在该项目下创建…

JAVA BIO深入剖析

目录 JAVA BIO深入剖析1 Java BIO 基本介绍2 Java BIO 工作机制3 传统的BIO编程实例回顾客户端案例如下服务端案例如下小结 4 BIO模式下多发和多收消息客户端代码如下服务端代码如下小结 5 BIO模式下接收多个客户端概述客户端案例代码如下服务端案例代码如下小结 6 伪异步I/O编…

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)

前言: 这是程序环境和预处理的下半篇文章。至此,关于c语言知识点:从编译到运行的过程已讲解完毕。传送🚪,上半篇: http://t.csdnimg.cn/hvxmr 本章涉及的知识点: 宏和函数对比、命名约定、#undef、命令行定…

Linux 系统 SSH 和 SCP 服务器搭建、配置、访问以及出现的问题

SSH是Secure Shell的缩写,是一种网络协议,用于通过本地或远程网络在计算机上进行远程登录和命令操作。SSH 是 Telnet 协议的演变:正如其名称所描述的,SSH 是安全的,并对通过网络传输的数据进行加密。 SSH 是目前较为可…

分布式-分布式事务理论、模型、方案、框架

一、分布式事务理论模型 分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的分布性,比如多个数据库,或者MySQL和Redis两种不同存储设备的数据一致性…

5. PyTorch——数据处理模块

1.数据加载 在PyTorch中,数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset类,实现自定义的数据集需要继承Dataset,并实现两个Python魔法方法: __getitem__:返回一条数据,或一个样本。obj[in…

鸿蒙开发之状态管理@Prop和@Link

一、用法 在父子组件需要进行数据同步的时候,可以通过Prop和Link装饰器来做到。在父组件中用State装饰,在自组件中用Prop或Link装饰。 结论:Prop用于子组件只监听父组件的数据改变而改变,自己不对数据改变 Link用于子组件与父组…

Skype的介绍及使用

介绍及使用说明 Windows自带的Skype是一款全球通用的即时通讯软件,它可以让用户通过文字、语音和视频进行免费的在线沟通。下面是Skype的使用说明: 1.安装和登录:如果你的Windows系统中没有预装Skype,你可以在Microsoft官…

锁定屏幕与挂起

概要: 本篇主要讲述Ubuntu22.04中的锁定屏幕和挂起 锁定屏幕就是大家通常所说的息屏、锁屏,英文单词是lock 挂起一般也被称为休眠、睡眠,英文单词是suspend 一、锁定屏幕 1、CtrlL 按下键盘上的CtrlL键,即可锁定屏幕&#x…

【华为鸿蒙系统学习】- HarmonyOS4.0开发工具和环境配置问题总结|自学篇

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 💫个人格言:"没有罗马,那就自己创造罗马~" 目录 官方链接 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 安装教程 (…

【PTA刷题+代码+详解】求二叉树度为1的结点个数(递归法)

文章目录 题目C代码详解 题目 在二叉树T中,其度为1的结点是指某结点只有左孩子或只有右孩子。利用递归方法求二叉树T的度为1的结点个数。 1)如果TNULL,则是空树,度为1的结点个数为0,返回值为0; 2&#xff0…

Python爬虫实战 | 爬取拼多多商品的详情价格SKU数据

本案例将为大家演示如何爬取拼多多商品的详情数据。目的是爬取大量的商品以及商品的评论,所以在程序设计上要考虑到该爬虫的高并发以及持久化存储。爬虫工具选用了Scrapy框架,以满足爬虫的高并发请求任务;持久化存储用了MongoDB,对…

python:五种算法(SSA、WOA、GWO、PSO、GA)求解23个测试函数(python代码)

一、五种算法简介 1、麻雀搜索算法SSA 2、鲸鱼优化算法WOA 3、灰狼优化算法GWO 4、粒子群优化算法PSO 5、遗传算法GA 二、5种算法求解23个函数 (1)23个函数简介 参考文献: [1] Yao X, Liu Y, Lin G M. Evolutionary programming made…

vue 集成行政区域选择插件region和数据回显

故事:最近,项目需要进行行政区域围栏的绘制,由于老旧项目是利用js保存全国行政区域地址和编码,在选择器select进行匹配显示,但此方法复杂,因此选择集成区域插件region 步骤一:用命令安装region…

Vue3-09-条件渲染-v-show 的基本使用

v-show 的作用 v-show 可以根据条件表达式的值【展示】或【隐藏】html 元素。v-show 的特点 v-show 的实现方式是 控制 dom 元素的 css的 display的属性, 因此,无论该元素是否展示,该元素都会正常渲染在页面上, 当v-show 的 条件…

如何通过 SSH 访问 VirtualBox 的虚机

VirtualBox 是一款免费虚机软件。在用户使用它安装了 linux 以后,它默认只提供了控制台的管理画面。 直接使用控制台管理 Linux 没有使用诸如 putty 或者 vscode 这样的 ssh 远程管理工具方便。那么可不可以直接使用 ssh 访问 VirtualBox 上的 Linux 呢&#xff1f…