可靠数据传输协议-GBN 协议的设计与实现

可靠数据传输协议-GBN 协议的设计与实现

首先最重要的代码:github地址

一、 实验目的

理解滑动窗口协议的基本原理;掌握 GBN 的工作原理;掌握基于 UDP 设计并实现一个 GBN 协议的过程与技术。

二、 实验内容

实现了GBN协议,模拟丢包,并支持双向传输,并改进为了SR协议

  1. 基于UDP设计一个简单的GBN协议,实现单向可靠数据传输(服务器到客户的数据传输)。
  2. 模拟引入数据包的丢失,验证所设计协议的有效性。
  3. 改进所设计的 GBN 协议,支持双向数据传输;(选作内容,加分 项目,可以当堂完成或课下完成)
    4)将所设计的 GBN 协议改进为 SR 协议。( 选作内容,加分项目, 可以当堂完成或课下完成)

三、实验过程及结果

  1. 流程:首先client端将需要发送的文件解析成byte数组,调用send发送server端使用receive接收,当数据传输结束后server端利用在之前receive获取的port和address使用重载的send方法返回数据,client直接调用receive即可。
    其中定时器采用多线程,没发送一个都启用一个定时间线程,并将发送的socket,packet,序列号等传入,启动线程即使其sleep,当超多时间后判断是否接收到ack,没有则重传并再次启动定时器。
    对ack的接收也使用多线程处理,使其发送的同时可以接收。
    需要注意的是由于多个线程都用到了ackMap(记录接收ack状况的map)所以需要使用线程安全的ConcurrentHashMap
  2. 数据分组格式
    Length	Seq	Data	End
    Length:数据长度,2字节,<=1024;
    Seq:序列号,1字节,0 ~ 255;
    Data:数据,最大1024自己;
    End:结尾标志,1字节,0表示结尾,1表示非结尾
  3. 确认分组格式
    Seq	End
    Seq:序列号,1字节,0 ~ 255;
    End:结尾标志,1字节,0表示结尾,1表示非结尾
  4. 协议两端程序流程图
    可靠数据传输协议-GBN 协议的设计与实现_第1张图片
  5. 协议典型交互过程
    发送方的事件与动作:
    • 从上层收到数据。当从上层接收到数据后,SR 发送方检查下一个可用于该分组的序号。如果序号位于发送方的窗口内,则将数据打包并发送;否则就像在 GBN 中一样,要么将数据缓存,要么将其返回给上层以便以后传输。
    • 超时。定时器再次被用来防止丢失分组。然而,现在每个分组必须拥有其自己的逻辑定时器,因为超时发生后只能发送一个分组。
    • 收到ACK。如果收到 ACK,倘若该分组序号在窗口内,则 SR 发送方将那个被确认的分组标记为已接收。若该分组的序号等于 send_base,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。
    接收方的事件与动作:
    • 序号在 [rcv_base, rcv_base+N-1] 内的分组被正确接收。在此情况下,收到的分组落在接收方的窗口内,一个选择 ACK 被回送给发送方。如果该分组以前没收到过,则缓存该分组。如果该分组的序号等于接收端的基序号(rcv_base),则该分组以及以前缓存的序号连续的(起始于 rcv_base 的)分组交付给上层。然后,接收窗口按向前移动分组的编号向上交付这些分组。
    • 序号在 [rcv_base-N, rcv_base-1] 内的分组被正确收到。在此情况下,必须产生一个 ACK,即使该分组是接收方以前确认过的分组。
    • 其他情况。忽略该分组。
  6. 数据分组丢失验证模拟方法
    设置数据包丢失率和ACK丢失率,使用Math.random生成0到1的随机数,只有当生成的随机数大于丢失率时才发送数据或ACK
    private static double sendLoss = 0.1;
    private static double recvLoss = 0.05;
  7. 程序实现的主要类(或函数)及其主要作用
    1)Server:服务器端,首先接收客户端发送的数据,之后发送数据给客户端
    2)Client:客户端,首先发送数据给服务器端,然后接收服务器返回的数据
    3)RecvAck:接收ack,继承自thread,参数为ackMap,datagramSocket。使用新的线程接收ack,注意要设置阻塞时间,否则接收完成后会阻塞住,ackMap的size为0并且收到ack的end为0时代表接收完成。注意当倒数几个有重发时,虽然接到了end是0但不是终止,所以要两个条件一起判断。
    4)TimeOut:定时器类,继承自thread,参数有datagramSocket,datagramPacket,sequence。每发送一个数据则启动一个定时器,睡眠100ms,之后判断是否接受到ack,没接受到则重传,并重新启动定时器
    5)PublicMethod:一些公共的方法,因为实现了双向数据传输,客户端与服务器端逻辑基本相同,都放在了公共方法里,其也是程序的重点,重要代码处理逻辑都在其中。
    a)ByteArrayOutputStream getByteArray(File file) :将要传输的文件转化成ByteArrayOutputStream方便以后传输时转化成数组处理
    通过FileInputStream读入文件并将其写入ByteArrayOutputStream
    b)void send(DatagramSocket socket, byte[] sendBytes, InetAddress address, int targetPort):client端发送函数,参数为UDP的socket,要传输的数据,目的地址,目的端口号。
    使用ackMap记录已经发送的分租收到ack的情况,创建RecvAck对象并start启动接收ack线程,注意必须使用线程安全的ConcurrentHashMap(开始没有发现,经常出现null,debug巨久),当其收到连续的ack时滑动窗口到第一个未收到ack的位置。可以发送的窗口大小为窗口大小减去ackMap的大小,接下来开始发送数据,按上面的数据格式创建报文,并发送,序列号最大为255,注意要循环序列号,之后发送报文并启动定时器线程,将socket和ackMap,packet,sequence传送给定时器并启动。当全部发送且ackMap里面没有数据时终止。
    c) byte[] receive(DatagramSocket socket):接受函数,参数为接受的socket,以byte数组返回接收到的数据。
    使用ByteArrayOutputStream存放接收到的数据,通过datagramSocket的receive接受数据,并获取其中的长度,序列号,是否结束。注意要判断收到序列号所在的位置,由于序列号是循环的,[rcv_base, rcv_base+N-1]和[rcv_base-N, rcv_base-1]的大小可能不同要进行分析。第二种情况发送只ack即可,第一种如果收到和base相同的则滑动接受窗口,不连续则缓存都要发送ack。都不满足则无需处理。当接接收超时时证明接收完成退出循环。(因为发送的ack可能没有到达所以不好用是否接收到全部数据判断,否则对面可能未接到ack而server已关闭不发送ack使对面陷入无限重传)
    d) void send(DatagramSocket socket, byte[] sendBytes):server端的发送函数,参数只有socket和数据,实际为客户端socket的重载,其中目的地址和目的端口号有receive函数中解析出来,由于server端先接受,首先调用receive方法时即可获取targetPort和address。
    处理逻辑与client的send相同
    e) void recvByteMethod(FileOutputStream fileOutputStream, DatagramSocket socket):接受数据函数,使用wile(true)循环使得可以一直处于接受状态,否则必须在server开始后再启动client也可以防止server端回传数据时client已关闭的问题。

你可能感兴趣的:(可靠数据传输协议-GBN 协议的设计与实现)