流水线可靠数据传输机制演示系统——GBN

这个GBN(回退N步)的演示可以访问教材的配套资源网站,网站上有一个GBN交互式动画演示,以下程序是仿照这个实现的。教材就是下面的第一条参考文献,网址就在教材这一章的后面。

一、问题描述

熟悉流水线可靠数据传输(GBN)机制,并适当形式演示其基本原理。在回退N步(GBN)协议中,允许发送方发送多个分组(当有多个分组可用时)而不需等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数N,是一种解决流水线的差错恢复的基本方法。主要问题在于累计确认机制和定时器超时重传机制,从而实现流水线的可靠传输(GBN)机制。

二、基本要求

1.本课题的任务是熟悉流水线可靠数据传输(GBN)机制,并适当形式演示其基本原理。

2.该演示系统至少具有以下功能:

(1)能够通过图形界面组件形象地显示GBN的滑动窗口机制。

(2)能实时显示发送方、接收方所维护的主要变量的值。

(3)能人工干预数据是否出错、是否丢失。

(4)能人工设定窗口的大小、序号空间的大小,并判断是否合理。

3.开发工具不限,但建议使用Java/C++等实现。

三、设计思想

在回退N步(GBN)协议中,允许发送方发送多个分组(当有多个分组可用时)而不需等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数N

如果将基序号(base)定义为最早未确认分组的序号,将下一个序号(nextseqnum)定义为最小的未使用序号(即下一个待发分组的序号),则可以将序号范围分割成4段。在[0,base-1]段内的序号对应于已经发送的并被确认的分组。[base,nexrseqnum-1]段内对应已经发送但未被确认的分组。[nextseqnum,base+N-1]段内的序号能用于那些要被立即发送的分组,如果有数据来自上层的话。最后,大于或者等于base+N的序号是不能使用的,直到当前流水线中未被确认的分组已得到确认为止。

图3.1 在GBN中发送方看到的序号

如图3.1,那些已被发送但还未被确认的分组的许可序号范围可以被看成是一个在序号范围内长度为N的窗口。。随着协议的运行,该窗口在序号空间向前滑动。

GBN发送方必须响应三种类型的事件:

3.2 GBN发送方的拓展FSM描述 

图3.3 GBN接收方的拓展FSM描述

  • 上层的调用。当上层调用rdt_send()时,发送方首先检查发送窗口是否已满,即是否有N个已发送但未被确认的分组。如果窗口未满,则产生一个分组并将其发送,并相应地更新变量。如果窗口已满,发送方只需将数据返回给上层,隐式地指示上层该窗口已满。然后上层可能会过一会儿再试。在实际实现中,发送方更可能缓存(并不立即发送)这些数据,或者使用同步机制(如一个信号量或标志)允许上层在仅当窗口不满时才调用rdt_send()。
  • 收到一个ACK。在GBN协议中,对序号为n的分组的确认采取累积确认(cumulative acknowledgment)的方式,表明接收方已正确接收到序号为 n 的以前且包括 n 在内的所有分组。
  • 超时事件。协议的名字“回退 N 步”来源于出现丢失和时延过长分组时发送方的行为。就像在停等协议中那样,定时器将再次用于恢复数据或确认分组的丢失。如果出现超时,发送方重传所有已发送但未被确认过的分组。图3.2发送方仅使用一个定时器,如果收到了一个 ACK,但仍有已发送但未被确认的分组,则定时器被重新启动。如果没有已发送但未被确认的分组,停止该定时器。

实现思想:使用JLabel模拟设定发送端和接收端还有中间传输的数据包,设置定时器。对不同状态的包使用不同颜色样式加以区别,通过对数据包定位的判定对发送段接收端和定时器进行不同操作和判定重发,根据GBN协议的原理,利用图形界面对于操作进行相应的控制和演示。

四、系统结构

主要包含了5个类:

(1)Menu类:菜单图形界面,使用户能够输入窗口大小和序列空间,并对用户输入的数据进行合理性判定。

(2)GbnDemoSystem类:模拟的GBN的演示图形界面,在窗口中可视化模拟GBN机制是怎样传输,可以通过按钮对数据包具体控制遇到的不同的情况,并演示GBN是如何处理这些不同情况的。

(3)SenderThread类:模拟GBN协议中的发送端,进行发送分组操作。

(4)MoveThread类:实现图形界面中各种JLabel移动的操作,包括接收端、发送端接收和发送的各种操作进行定义。

(5)TimerTest类:定义的定时器。若传输出了问题,定时器超时重传的操作。

五、程序流程(或模块划分)

图5.1 各类之间的调用关系

图5.2 系统基本流程图

六、源程序

Menu.java

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Menu implements ActionListener  {
    JFrame menu;
    JLabel windowsize1,seqsize1;//请输入要设定的窗口大小,请输入要设定的序号空间大小
    JTextField windowsize2,seqsize2;
    JButton confirm;//确认按钮
    boolean a = false;

    public Menu() {
        menu = new JFrame("流水线可靠数据传输机制演示系统——GBN");
        menu.setLayout(null);//设置此容器的布局管理器。
        windowsize1 = new JLabel("请输入要设定的窗口大小:");
        seqsize1 = new JLabel("请输入要设定的序号空间大小:");
        windowsize2 = new JTextField(5);
        seqsize2 = new JTextField(5);
        confirm = new JButton("确认");

        seqsize1.setSize(200, 100);
        seqsize1.setLocation(150, 10);
        seqsize2.setSize(200, 40);
        seqsize2.setLocation(350, 40);
        windowsize1.setSize(200, 100);
        windowsize1.setLocation(150, 100);
        windowsize2.setSize(200, 40);
        windowsize2.setLocation(350, 130);
        confirm.setSize(200, 50);
        confirm.setLocation(230, 220);
        confirm.addActionListener(this);//给confirm按钮等添加事件监听接口,this表示当前类的对象

        menu.add(seqsize1);
        menu.add(seqsize2);
        menu.add(windowsize1);
        menu.add(windowsize2);
        menu.add(confirm);

        menu.setSize(700,400);
        menu.setLocationRelativeTo(null);//定位屏幕中央
        menu.setVisible(true);
        menu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点关闭图标时关闭窗口,退出程序。
    }

    public static void main(String[] args) {
        new Menu();
    }

    public void actionPerformed(ActionEvent e) {//发生操作时调用。
        if (e.getSource() == confirm) {//最初发生 Event 的对象。getScource方法通过点击事件获取点击事件对象,if语句判断点击事件对象是不是我们想得到的confirm这个按钮
            int windowsize = Integer.parseInt(windowsize2.getText().trim());//trim():返回字符串的副本,忽略前导空白和尾部空白。
            int seqsize = Integer.parseInt(seqsize2.getText().trim());
            boolean t=judge(seqsize);//判断序号空间大小是不是2的次方
            if (windowsize <= seqsize/2 && t) {
                new GbnDemoSystem(windowsize,seqsize);
            }
            else if (windowsize > seqsize/2) {
                JOptionPane.showMessageDialog(menu, "窗口的值应当小于分组的二分之一!");//JOptionPane 有助于方便地弹出要求用户提供值或向其发出通知的标准对话框。调出标题为 "Message" 的信息消息对话框。
                return;//没有将会导致menu关闭
            }else if(!t) {
                JOptionPane.showMessageDialog(menu, "序号空间的大小应为2的k次方");
                return;//没有将会导致menu关闭
            }
            menu.setVisible(false);//关闭
        }
    }

    public boolean judge(int n){
        while(true){
            int m=n%2;
            n=n/2;
            if(m==1){
                a=false;
                break;
            }if(n==2){
                a=true;
                break;
            }
        }
        return a;
    }
}

GbnDemoSystem.java

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class GbnDemoSystem implements ActionListener,MouseListener{
    JFrame f;
    JPanel pl1,pl2,pl3,pl4;//pl1放置按钮、pl2放置分组、pl3放置样例说明、pl4放置输出文本框
    JButton sendNew,faster,slower,error,kill,reset;//按钮
    JLabel[] Jsender,Jserver,Jmove;//发送端、接收端、移动组件
    JLabel colorexample1,colorexample2,colorexample3,colorexample4,colorexample5,colorexample6,exampl1,exampl2,exampl3,exampl4,exampl5,exampl6,window1,window2;//示例及两个窗口框
    JTextArea display;//输出框  JTextArea 是一个显示纯文本的多行区域。
    JScrollPane jscrollpane;//提供轻量级组件的 scrollable 视图。JScrollPane 管理视口、可选的垂直和水平滚动条以及可选的行和列标题视口。
    int N; //窗口大小
    int seqsize; //序号大小
    int base=1;//基序号即窗口的第一个分组的序号
    int nextseqnum=1;//最小的未使用的分组
    boolean[] Send ;//表示发送端端收到正确数据
    boolean[] received;//表示接收端收到正确数据
    boolean[] moveFlag;//moveFlag表示数据有效,是正确的
    boolean[] select; //判断是否被选中
    boolean[] se; //发送标志位,判断是否已经发送便于重传
    SenderThread senderthread;
    MoveThread[] move;
    int z = 20;//加速

    public GbnDemoSystem(int N, int seqsize) {
        this.seqsize = seqsize;
        this.N = N;
        Flag();
        GbnDemoSystem();
    }

    public void Flag(){//初始化
        move = new MoveThread[seqsize];
        Send = new  boolean [seqsize];
        received = new  boolean [seqsize];
        moveFlag = new  boolean [seqsize];
        se = new  boolean [seqsize];
        for (int i = 0; i < seqsize; i++) {
            Send[i] = false;
            received[i] = false;
            moveFlag[i] = true;
            se[i] = false;
        }
    }

    public void GbnDemoSystem()  {
        f = new JFrame("流水线可靠数据传输机制演示系统——GBN");
        f.setLayout(null);
        select = new  boolean [seqsize];
        for (int i = 0; i < seqsize; i++) {//初始化,都未选中
            select[i] = false;
        }
        pl1 = new JPanel();
        pl2 = new JPanel();
        pl3 = new JPanel();
        pl4 = new JPanel();
        colorexample1 = new JLabel();
        colorexample2 = new JLabel();
        colorexample3 = new JLabel();
        colorexample4 = new JLabel();
        colorexample5 = new JLabel();
        colorexample6 = new JLabel();
        exampl1 = new JLabel("Packet");
        exampl2 = new JLabel("Received");
        exampl3 = new JLabel("Ack");
        exampl4 = new JLabel("Ack Received");
        exampl5 = new JLabel("Select");
        exampl6 = new JLabel("Error");
        window1 = new JLabel();
        window2 = new JLabel();
        sendNew = new JButton("发送新分组");
        error = new JButton("错误");
        faster = new JButton("加快");
        slower = new JButton("变慢");
        kill = new JButton("丢失");
        reset = new JButton("重置");
        display = new JTextArea(22, 40);
        jscrollpane = new JScrollPane(display);

        window1.setOpaque(false);//如果为 true,则该组件绘制其边界内的所有像素
        window1.setBorder(BorderFactory.createLineBorder(Color.gray, 2));//setBorder设置此组件的边框。createLineBorder(Color colorexamplelor, int thickness)创建一个具有指定颜色和宽度的线边框。
        window2.setOpaque(false);
        window2.setBorder(BorderFactory.createLineBorder(Color.gray, 2));//灰边框
        colorexample1.setOpaque(true);
        colorexample1.setBorder(BorderFactory.createLineBorder(Color.black, 2));//黑边框
        colorexample1.setBackground(Color.cyan);//青色
        colorexample1.setSize(20, 20);
        colorexample1.setLocation(10, 10);
        colorexample2.setOpaque(true);
        colorexample2.setBorder(BorderFactory.createLineBorder(Color.black, 2));
        colorexample2.setBackground(Color.red);
        colorexample2.setSize(20, 20);
        colorexample2.setLocation(85, 10);
        colorexample3.setOpaque(true);
        colorexample3.setBorder(BorderFactory.createLineBorder(Color.black, 2));
        colorexample3.setBackground(Color.yellow);
        colorexample3.setSize(20, 20);
        colorexample3.setLocation(180, 10);
        colorexample4.setOpaque(true);
        colorexample4.setBorder(BorderFactory.createLineBorder(Color.black, 2));
        colorexample4.setBackground(Color.BLUE);
        colorexample4.setSize(20, 20);
        colorexample4.setLocation(240, 10);
        colorexample5.setOpaque(true);
        colorexample5.setBorder(BorderFactory.createLineBorder(Color.black, 2));
        colorexample5.setBackground(Color.green);
        colorexample5.setSize(20, 20);
        colorexample5.setLocation(355, 10);
        colorexample6.setOpaque(true);
        colorexample6.setBorder(BorderFactory.createLineBorder(Color.black, 2));
        colorexample6.setBackground(Color.gray);
        colorexample6.setSize(20, 20);
        colorexample6.setLocation(430, 10);

        window1.setSize(N*24, 44);
        window1.setLocation(6, 25);
        window2.setSize(24, 44);
        window2.setLocation(6, 255);
        exampl1.setSize(50, 35);
        exampl1.setLocation(35, 5);
        exampl2.setSize(70, 35);
        exampl2.setLocation(110, 5);
        exampl3.setSize(50, 35);
        exampl3.setLocation(205, 5);
        exampl4.setSize(80, 35);
        exampl4.setLocation(265, 5);
        exampl5.setSize(50, 35);
        exampl5.setLocation(380, 5);
        exampl6.setSize(50, 35);
        exampl6.setLocation(455, 5);

        pl2.add(window1);
        pl2.add(window2);
        add_packet();
        pl1.setBorder(BorderFactory.createLineBorder(Color.black, 1));
        pl2.setLayout(null);
        pl2.setBorder(BorderFactory.createLineBorder(Color.black, 1));
        pl3.setLayout(null);
        pl3.setBorder(BorderFactory.createLineBorder(Color.black, 1));
        display.setEditable(false);//设置判断此文本组件是否可编辑的标志。

        pl1.setBounds(20, 5, 500, 40);
        pl2.setBounds(20, 45, 500, 320);
        pl3.setBounds(20, 380, 500, 40);
        pl4.setBounds(20, 430, 500, 400);

        pl1.add(sendNew);
        pl1.add(faster);
        pl1.add(slower);
        pl1.add(error);
        pl1.add(kill);
        pl1.add(reset);
        pl3.add(colorexample1);
        pl3.add(exampl1);
        pl3.add(colorexample2);
        pl3.add(exampl2);
        pl3.add(colorexample3);
        pl3.add(exampl3);
        pl3.add(colorexample4);
        pl3.add(exampl4);
        pl3.add(colorexample5);
        pl3.add(exampl5);
        pl3.add(colorexample6);
        pl3.add(exampl6);
        pl4.add(jscrollpane);

        f.add(pl1);
        f.add(pl2);
        f.add(pl3);
        f.add(pl4);

        sendNew.addActionListener(this);
        faster.addActionListener(this);
        slower.addActionListener(this);
        error.addActionListener(this);
        kill.addActionListener(this);
        reset.addActionListener(this);

        f.setSize(600,900);
        f.setLocationRelativeTo(null);//定位屏幕中央
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private void add_packet() {//向pl2添加方框
        Jsender = new JLabel[seqsize];
        Jserver = new JLabel[seqsize];
        Jmove = new JLabel[seqsize];
        int x = 10;
        int j = 24;
        String z = "0";
        int y = Integer.parseInt(z);
        for(int i = 0; i < seqsize; i++) {
            //移动方框
            Jmove[i] = new JLabel(z);//创建具有指定文本的 JLabel 实例。
            Jmove[i].setOpaque(true);//如果为 true,则该组件绘制其边界内的所有像素。
            Jmove[i].setBorder(BorderFactory.createLineBorder(Color.black, 2));
            Jmove[i].setBackground(Color.cyan);
            Jmove[i].setSize(17, 35);
            Jmove[i].setLocation(x, 30);
            pl2.add(Jmove[i]);
            Jmove[i].setVisible(false);
            //发送方方框
            Jsender[i] = new JLabel(z);
            Jsender[i].setOpaque(true);
            Jsender[i].setBorder(BorderFactory.createLineBorder(Color.black, 2));
            Jsender[i].setSize(17, 35);
            Jsender[i].setLocation(x, 30);
            pl2.add(Jsender[i]);
            //接收方方框
            Jserver[i] = new JLabel(z);
            Jserver[i].setOpaque(true);
            Jserver[i].setBorder(BorderFactory.createLineBorder(Color.black, 2));
            Jserver[i].setSize(17, 35);
            Jserver[i].setLocation(x, 260);
            pl2.add(Jserver[i]);
            Jmove[i].addMouseListener(this);
            //为下一次循环创造条件
            x=x+j;//x坐标向右+24
            y ++;//y为z的int型
            z = String.valueOf(y);//string型的z+1

        }
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == sendNew) {
            display.append("         已点击发送数据包,数据包正在发送");//append将给定文本追加到文本区的当前文本。
            display.append("\n");

            senderthread = new SenderThread(this, 0);
            senderthread.type=1;//if(type==1)发送分组
            senderthread.start();
            int l=getbase()-1,h=nextseqnum-1;
            display.append("          base:"+l+"      nextseqnum:"+h+"\n");
        }
        else if (e.getSource() == error) {
            display.append("         数据包错误");
            display.append("\n");
            for(int z = 0; z<seqsize;z++) {
                if(select[z]) {
                    moveFlag[z] = false;
                    Jmove[z].setBackground(Color.gray);
                }
            }
        }
        else if (e.getSource() == faster) {
            display.append("         已点击加快按钮,数据包速度加快");
            display.append("\n");
            z-=2;
        }
        else if (e.getSource() == slower) {
            display.append("         已点击变慢按钮,数据包速度减慢");
            display.append("\n");
            z+=2;
        }
        else if (e.getSource() == kill) {
            display.append("         数据包丢失");
            display.append("\n");
            for(int k = 0; k<seqsize;k++) {
                if(select[k]) {
                    pl2.remove(Jmove[k]);
                    pl2.validate();//验证此容器及其所有子组件。使用 validate 方法会使容器再次布置其子组件。
                    pl2.repaint();//重绘此组件。
                    move[k].stop();
                    select[k] = false;
                }else {

                }
            }
        }
        else if (e.getSource() == reset) {
            f.setVisible(false);
            f.dispose();//释放由此 Window、其子组件及其拥有的所有子组件所使用的所有本机屏幕资源。
            new GbnDemoSystem(N,seqsize);
        }
    }

    public boolean getmoveFlag(int i) {
        return moveFlag[i];
    }

    public int getbase(){
        return base;
    }

    public void mouseClicked(MouseEvent e) {
        for(int k = 0; k<seqsize;k++) {
            if (e.getSource() == Jmove[k]) {
                Jmove[k].setBackground(Color.green);//选中变绿
                select[k] = true;
                display.append("         已选中该数据包,可以单机按钮'错误'或'丢失'进行相关操作。");
                display.append("\n");
            }
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseExited(MouseEvent e) {
        // TODO Auto-generated method stub

    }
}

SenderThread.java

import java.awt.Color;
public class SenderThread extends Thread{
    GbnDemoSystem gbn;
    static int type;
    TimerTest timer;//定时器
    public SenderThread(GbnDemoSystem gbn,int type) {
        this.gbn = gbn;
        SenderThread.type = type;
    }

    public void run() {
        timer = new TimerTest(gbn);
        if(type==1){//发送分组
            if(gbn.nextseqnum == gbn.base) {
                timer.clock();
                gbn.display.append("         定时器启动");
                gbn.display.append("\n");
            }
            if(gbn.nextseqnum<gbn.seqsize+1){
                if(gbn.nextseqnum<gbn.base+gbn.N){//未超过窗口大小
                    gbn.move[gbn.nextseqnum-1] = new MoveThread(gbn.Jmove[gbn.nextseqnum-1], 1, gbn);
                    gbn.move[gbn.nextseqnum-1].start();
                    gbn.Jsender[gbn.nextseqnum -1].setBackground(Color.cyan);
                    gbn.nextseqnum++;
                }
                else{//超过窗口大小
                    gbn.display.append("         最多发送"+gbn.N+"个分组!");
                    gbn.display.append("\n");
                }
            }
            else{//超过最大
                gbn.display.append("         已超出范围!");
                gbn.display.append("\n");
            }
        }
    }
}

 MoveThread.java

import javax.swing.*;
import java.awt.*;

public class MoveThread extends Thread {
    JLabel lab;
    int Eflag; //事件的分类
    GbnDemoSystem gbn;
    public int y;
    public int x;
    static int ACK = 0;
    int b = 30,c;//用于window定位
    TimerTest timer;//定时器

    public MoveThread(JLabel lab, int Eflag, GbnDemoSystem gbn) {
        this.lab = lab;
        this.Eflag = Eflag;
        this.gbn = gbn;
    }

    public void run() {
        timer = new TimerTest(gbn);
        x = lab.getBounds().x;//getBounds().x 指的是某一个组件左上角的x坐标
        y = lab.getBounds().y;//getBounds().y 指的是某一个组件左上角的y坐标
        if (Eflag == 1) {//下降
            lab.setVisible(true);
            while (y < 260) {
                y = y + 1;
                lab.setLocation(x, y);
                try {
                    Thread.sleep(gbn.z);
                }
                catch (InterruptedException e) {

                }
            }
            for (int i = 0; i < gbn.seqsize; i++) {
                if (lab.equals(gbn.Jmove[i])) {
                    gbn.moveFlag[i] = gbn.getmoveFlag(i);
                    gbn.se[i] = true;//boolean[] se; //发送标志位,判断是否已经发送便于重传
                    if(i==0) {//第一个发送的数据包
                        if (gbn.moveFlag[i]) {
                            gbn.Jserver[i].setBackground(Color.red);
                            gbn.Jmove[i].setBackground(Color.yellow);
                            gbn.received[i] = true;//表示接收端收到正确数据
                            gbn.display.append("         接收端已成功接收数据包");
                            gbn.display.append("\n");
                            gbn.display.append("         返回ACK");
                            gbn.display.append("\n");
                            gbn.display.append("         接收窗口滑动");
                            gbn.display.append("\n");
                            for (int j = 0; j < i; j++) {
                                if (gbn.received[j]) {
                                    b += 24;
                                }
                            }
                            try {//返回
                                if (!gbn.received[i + 1] && !gbn.Send[i]) {
                                    gbn.window2.setLocation(b, 255);
                                    gbn.received[i] = true;
                                    gbn.move[i] = new MoveThread(lab, 2, gbn);
                                    gbn.move[i].start();
                                }else {
                                    gbn.move[i] = new MoveThread(lab, 2, gbn);
                                    gbn.move[i].start();
                                }
                            } catch (Exception e) {
                            }
                        }
                        else { //错误数据包
                            gbn.received[i] = false;
                            gbn.display.append("         接收端接收到错误数据包,丢弃数据包,等待定时器超时");
                            gbn.display.append("\n");
                            gbn.pl2.remove(gbn.Jmove[i]);
                            gbn.pl2.validate();
                            gbn.pl2.repaint();
                            gbn.move[i].stop();
                        }
                    }else {  //除了0之外的其他数据包
                        if (gbn.moveFlag[i] && gbn.received[i-1]) { //数据包正确并且前面的一个也接收到了
                            gbn.Jserver[i].setBackground(Color.red);
                            gbn.Jmove[i].setBackground(Color.yellow);
                            gbn.received[i] = true;
                            gbn.display.append("         接收端已成功接收数据包");
                            gbn.display.append("\n");
                            gbn.display.append("         返回ACK");
                            gbn.display.append("\n");
                            gbn.display.append("         接收窗口滑动");
                            gbn.display.append("\n");
                            for (int j = 0; j < i; j++) {
                                if (gbn.received[j]) {
                                    b += 24;
                                }
                                else {
                                    break;
                                }
                            }
                            try {
                                if (!gbn.received[i + 1] && !gbn.Send[i]) {
                                    gbn.window2.setLocation(b, 255);
                                    gbn.received[i] = true;
                                    gbn.move[i] = new MoveThread(lab, 2, gbn);
                                    gbn.move[i].start();
                                }else {
                                    gbn.received[i] = false;
                                    gbn.Jmove[i].setBackground(Color.yellow);
                                    gbn.move[i] = new MoveThread(lab, 2, gbn);
                                    gbn.move[i].start();
                                    gbn.display.append("\n");
                                }
                            } catch (Exception e) {
                                gbn.received[i] = false;
                                gbn.Jmove[i].setBackground(Color.yellow);
                                gbn.move[i] = new MoveThread(lab, 2, gbn);
                                gbn.move[i].start();
                                gbn.display.append("\n");
                            }
                        }else if(!gbn.moveFlag[i]){ //错误数据包
                            gbn.received[i] = false;
                            gbn.display.append("         接收端接收到错误数据包,丢弃数据包,等待定时器超时");
                            gbn.display.append("\n");
                            gbn.pl2.remove(gbn.Jmove[i]);
                            gbn.pl2.validate();
                            gbn.pl2.repaint();
                            gbn.move[i].stop();
                        }else { //前一个没有接收成功返回相应正确ACK
                            gbn.Jmove[i].setBackground(Color.yellow);
                            if(!gbn.received[i-1]||gbn.base == i){ //若上一个接收方没有接收到,直接丢弃
                                gbn.display.append("         没有上一个成功接收的数据包,丢弃数据包,等待定时器超时");
                                gbn.display.append("\n");
                                gbn.pl2.remove(gbn.Jmove[i]);
                                gbn.pl2.validate();
                                gbn.pl2.repaint();
                                gbn.move[i].stop();
                            }else { //若上一个接收方收到了接收到 返回上一个成功的
                                gbn.Jmove[i].setText(String.valueOf(gbn.base-2));
                                gbn.display.append("         返回上一个成功接收的ACK");
                                gbn.display.append("\n");
                            }
                            gbn.Send[i] = false;
                            gbn.display.append("         接收端接收数据包失败");
                            gbn.display.append("\n");
                            gbn.move[i] = new MoveThread(lab, 2, gbn);
                            gbn.move[i].start();
                        }
                    }
                }
            }
        }
        else if (Eflag == 2) {//上升
            while (y >= 30) {
                y = y - 1;
                lab.setLocation(x, y);
                try {
                    Thread.sleep(gbn.z);
                } catch (InterruptedException e1) {

                }
            }
            if (y < 30) {
                for (int i = 0; i < gbn.seqsize; i++) {
                    if (lab.equals(gbn.Jmove[i])) {
                        if (gbn.moveFlag[i]) {//数据有效,是正确的
                            if(i>0) {//其他ACK返回
                                if(gbn.Send[i - 1]) { //如果前面的接收端成功接收到了
                                    gbn.Send[i] = true;
                                    gbn.display.append("         发送端成功接收到ACK");
                                    gbn.display.append("\n");
                                    gbn.display.append("         发送窗口滑动");
                                    gbn.display.append("\n");
                                }
                                if (gbn.Send[i]) { //发送端成功接收
                                    timer.Tflag = false;//上一个定时器任务关闭
                                    gbn.display.append("         定时器终止");
                                    gbn.display.append("\n");

                                    if(i==gbn.seqsize-1) {

                                    }else {
                                        if(!gbn.Send[i+1] && gbn.se[i+1]) {
                                            timer.clock();//下一个定时器开启
                                            gbn.display.append("         定时器启动");
                                            gbn.display.append("\n");
                                        }
                                    }
                                    for (int j = 0; j < gbn.seqsize; j++) {
                                        if (lab.equals(gbn.Jmove[j])) {
                                            ACK = j + 1;
                                        }
                                        gbn.senderthread.type = 2;
                                        if (gbn.senderthread.type == 2) {// 收到正确ACK
                                            gbn.base = ACK + 1;
                                            lab.setVisible(false);
                                            for(int l = 0;l<ACK;l++) { //累计确认
                                                gbn.Jsender[l].setBackground(Color.blue);
                                            }
                                            if(gbn.base + gbn.N <= gbn.seqsize+1) {//避免窗口框超出序号空间
                                                c = 6+ACK*24;
                                                gbn.window1.setLocation(c, 25);
                                            }
                                        }else {
                                            gbn.Jsender[i].setBackground(Color.blue);
                                            lab.setVisible(false);
                                        }
                                    }

                                }else if(gbn.received[i]) { //接收端成功接收
                                    for (int j = 0; j < gbn.seqsize; j++) {
                                        if (lab.equals(gbn.Jmove[j])) {
                                            ACK = j + 1;
                                        }
                                        gbn.senderthread.type = 2;
                                        if (gbn.senderthread.type == 2) {// 收到正确ACK
                                            gbn.base = ACK + 1;
                                            lab.setVisible(false);
                                            for(int l = 0;l<ACK;l++) { //累计确认
                                                gbn.Jsender[l].setBackground(Color.blue);
                                            }
                                            if(gbn.base + gbn.N <= gbn.seqsize+1) {
                                                c = 6+ACK*24;
                                                gbn.window1.setLocation(c, 25);
                                            }
                                        }else {
                                            gbn.Jsender[i].setBackground(Color.blue);
                                            lab.setVisible(false);
                                        }
                                    }

                                }else{ //都没有成功接收
                                    gbn.display.append("         接收端接收到错误ACK,丢弃ACK,等待定时器超时");
                                    gbn.display.append("\n");
                                    for (int j = 0; j < gbn.seqsize; j++) {
                                        if (lab.equals(gbn.Jmove[j])) {
                                            ACK = j;
                                        }
                                        gbn.senderthread.type = 3;
                                        if (gbn.senderthread.type == 3) {// 收到错误ACK
                                            lab.setVisible(false);
                                        }else {
                                            gbn.Jsender[i].setBackground(Color.blue);
                                            lab.setVisible(false);
                                        }
                                    }
                                }
                            }else {//第一个ACK返回
                                gbn.Send[i] = true;
                                gbn.display.append("         发送端成功接收到ACK");
                                gbn.display.append("\n");
                                gbn.display.append("         发送窗口滑动");
                                gbn.display.append("\n");

                                timer.Tflag = false;//上一个定时器任务关闭
                                gbn.display.append("         定时器终止");
                                gbn.display.append("\n");
                                if(!gbn.Send[i+1] && gbn.se[i+1]) { //如果下一个分组已经发送并且没有接收到ACK
                                    timer.clock();//下一个定时器开启
                                    gbn.display.append("         定时器启动");
                                    gbn.display.append("\n");
                                }
                                for (int j = 0; j < gbn.seqsize; j++) {
                                    if (lab.equals(gbn.Jmove[j])) {
                                        ACK = j + 1;
                                    }
                                    gbn.senderthread.type = 2;
                                    if (gbn.senderthread.type == 2) {// 收到正确ACK
                                        gbn.base = ACK + 1;
                                        lab.setVisible(false);
                                        gbn.Jsender[0].setBackground(Color.blue);
                                        gbn.window1.setLocation(30, 25);
                                    }else {
                                        gbn.Jsender[i].setBackground(Color.blue);
                                        lab.setVisible(false);
                                    }
                                }
                            }
                        }else { //错误ACK
                            gbn.Send[i] = false;
                            gbn.display.append("         收到错误ACK,丢弃错误ACK,等待定时器超时");
                            gbn.pl2.remove(gbn.Jmove[i]);
                            gbn.pl2.validate();
                            gbn.pl2.repaint();
                            gbn.move[i].stop();
                        }
                    }
                }
            }
        }

    }
}

 TimerTest.java

import java.awt.Color;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest extends Thread {
    GbnDemoSystem gbn;
    Timer timer = new Timer();
    boolean Tflag = true;

    public TimerTest(GbnDemoSystem gbn) {
        this.gbn = gbn;
    }

    public void clock(){
        Tflag = true;
        TimerTask task = new TimerTask() {//创建一个新的计时器任务。
            public void run() {
                for(gbn.getbase();gbn.base<gbn.nextseqnum;gbn.base++){//这个序号区间需要重传
                    gbn.moveFlag[gbn.getbase()-1] =true;//将数据有效标志置为ture,重传的数据是有效的
                    if(gbn.move[gbn.base-1].isAlive()){//防止页面过乱将需要重发的正在运行的线程关闭
                        gbn.move[gbn.base-1].stop();
                    }
                    gbn.pl2.add(gbn.Jmove[gbn.base-1]);
                    gbn.Jmove[gbn.base-1].setText(String.valueOf(gbn.base-1)); //将之前修改的返回的下标重传时改为正确的分组下标
                    gbn.Send[gbn.base-1] = false; //重置重传分组相关标志
                    gbn.pl2.validate();
                    gbn.pl2.repaint();
                    gbn.move[gbn.base-1] = new MoveThread(gbn.Jmove[gbn.base-1], 1, gbn);
                    gbn.move[gbn.base-1].y = 35;
                    gbn.move[gbn.base-1].x = gbn.Jmove[gbn.base-1].getLocation().x; //将重传的分组重新定位到发送端的位置
                    gbn.Jmove[gbn.base-1].setLocation(gbn.move[gbn.base-1].x, gbn.move[gbn.base-1].y);
                    gbn.Jmove[gbn.base-1].setBackground(Color.cyan);
                    gbn.move[gbn.base-1].start();
                    gbn.display.append("         定时器超时重传");
                    gbn.display.append("\n");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        timer.schedule(task, 1000*30);//安排在指定延迟后执行指定的任务。
        if(!Tflag) {
            task.cancel();//取消此计时器任务。
        }
    }
}

七、测试数据

输入窗口3,序号空间为8。

发送方浅蓝色代表已发送分组但未被确认

发送方深蓝色代表已发送分组并成功接收ACK

接收方红色代表成功接收到数据包

数据包浅蓝色代表正在向接送端发送的数据包

数据包黄色代表正在向发送端返回的ACK

数据包绿色代表被鼠标选中,可以进行丢失、错误等操作

数据包深灰色代表错误的数据包或ACK

八、测试情况

程序运行结果图:

1.输入窗口和序号空间,并检查其合理性

图8.1 输入窗口和序号空间

 

图8.2 检查序号空间

图8.3 检查输入窗口

2.输入窗口和序号空间后创建的图形界面

图8.4 输入窗口和序号空间后创建的图形界面

3.发送端发送数据包

图8.5 发送端发送数据包

4.接收端成功接收并返回ACK

图8.6 接收端成功接收并返回ACK

5.发送端成功接收ACK

图8.7 发送端成功接收ACK

6.数据包丢失

图8.8 数据包丢失

图8.9 数据包丢失后超时重传

图8.10重传完成

7.数据包出错

图8.11 数据包出错

图8.12 等待重传

8.发送大于窗口长度数量的数据包

图8.13 发送大于窗口长度数量的数据包

9.ACK丢失

图8.14 ACK丢失

图8.15 ACK丢失后结果

10.ACK出错

图8.16 ACK错误后结果

结 论

—— —— —— ——

参考文献

[1] [美]James·F.Kurose,Keith W.Ross (陈鸣 译)计算机网络自顶向下方法 (第七版) 机械工业出版社 2019

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值