编程实现可靠数据传输原理Go-Back-N

有一个计算机网络的实验,需要模拟GBN的过程,即在本地条件下模拟丢包重传机制。刚看到这个实验时有些不知所措,因为不知道怎样能实现丢包,在本地编程,即使是使用不可靠数据传输UDP的DatagramSocket,由于localhost到localhost走的是回环链路,也就是从客户机的应用层–传输层–网络层 到 服务器的网络层–传输层–应用层,这个肯定无法丢包,也用实验进行验证了(比如连续发送1万个DatagramPacket,发现每一个都被收到了)。

那么问题来了,怎样才能丢包?

通过前面的叙述,可知在链路上丢包是不可能的了,那我们可以从服务器端下手,也就是说,即使服务器收到了某个数据包,也不给客户机发ACK,那么到了客户机设置的定时器后,客户机自然就认为是丢包了。所以可以给服务器设置一个丢包概率,用Math.random()函数来实现,该函数返回一个从0到1之间的随机数,可用来设置“丢包”概率。如设置丢包概率为0.3:

1
2
3
4
5
if (Math.random()<=0.7){ 
//接收成功,发ack;
}else{
//呵呵,什么都不做
}

“丢包”问题解决了,下面就是怎么发送数据包,用之前学过的TCP的Socket也行,用UDP的DatagramSocket也行,这个实验就采用UDP编程吧,所以,下一个问题就是,如何用UDP编程。

同样也需要Socket(不理解Socket是什么的可以看前几篇文章),不过这次的是DatagramSocket,代表是UDP的数据报套接字。DatagramSocket要往外发送数据包,也就是DatagramPacket。举一个客户机可以向服务器发送数据并在服务器显示的例子。

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UDPClient { 
public static void main(String[] args) throws Exception {
//创建UDP的socket
DatagramSocket socket = new DatagramSocket();
//通过域名来获得服务器的IP地址
InetAddress serverAddress = InetAddress.getByName("localhost");
String str = "Hello Server, I am client";
//需要将发送的字符串转换成字节数组,才能在互联网中传输
byte[] data = str.getBytes();
//新建数据包,指明发送数据内容,发送的长度,服务器地址,服务器端口号
DatagramPacket packet = new DatagramPacket(data, data.length,
serverAddress, 8899);
//使用socket发送数据包
socket.send(packet);
}
}

对应服务器端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UDPServer { 
public static void main(String[] args) throws Exception {
//新建服务器端socket,并同时设置监听端口
DatagramSocket socket = new DatagramSocket(8899);
while (true){
byte[] data = new byte[1024];
//新建数据包,会把后面收到的内容放到data字节数组里,最大长度为data.length
DatagramPacket packet = new DatagramPacket(data, data.length);
//此方法为阻塞方法(block method),直到监听到数据包后才会往下执行,不然就一直等待,就像ServerSocket.accept()方法一样
socket.receive(packet);
//执行到这里说明监听到了数据包,并把其中内容转换为字符串
String result = new String(packet.getData());
//打印
System.out.println("receive result : " + result);
}
}
}

务必先运行服务器端,再运行客户端。运行结果如下:

image

若想服务器端给客户端发送数据,可同样操作。

利用DatagramSocket发送DatagramPacket的问题解决了,那么下一个问题,在GBN协议里面,客户端发送一个数据之后要设置定时器的,这个定时器怎么设置?

在java里有两种方式来设置定时器,首先是第一种方法,使用Timer结合ActionListener即可。

代码如下,其中值得注意的一点是Timer要导入java.swing这个包中的,而不是java.util这个包中的,否则不能正确运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class TimerMain {
public static void main(String[] args) {
//设置定时器,第一个参数为延迟执行时间,单位是毫秒;第二个参数为到时间后执行的动作,类型为ActionListener
Timer timer = new Timer(3000, new DelayActionListener());
//这个必须写,才可以使得timer生效
timer.start();
//为了不让程序结束,加个死循环,不然timer定时器还没到,主程序就结束了
while (true){
}
}
}
//自定义动作类实现ActionListener接口
class DelayActionListener implements ActionListener{
//此为回调方法,当时间到后自动执行actionPerformed方法
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("定时器时间到");
}
}

运行结果如下:每隔3秒执行一次。如果需要取消定时器,使用timer.stop()方法即可。

image

设置定时器的第二种方法是使用Timer结合TimerTask,在android里很常用,其实本来就是java的东西,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Timer; 
import java.util.TimerTask;
public class TimerTaskMain {
public static void main(String[] args) {
//使用TimerTask
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("timerTask定时器时间到");
}
};
Timer timer = new Timer();
//第一个参数为要执行的目标,第二个参数为第一次执行延迟的时间,第三个参数为循环执行的时间间隔,单位都是毫秒
timer.schedule(timerTask, 3000, 3000);
}
}

运行结果如下:每隔3秒执行一次。如果需要取消定时器,使用timerTask.cancel()方法即可。

image

有了如上这些东西,再结合GBN的原理和自己控制的逻辑操作,写GBN就应该可以实现了,就不贴完整代码了,自己实现印象更深刻些。

update——–

很多人跟我说你的 GBN 程序没源码。。。。虽然这里没贴源码,但我下面给了Github链接了,进去找找就好了,很明显的 GBN 开头的仓库就是了,一定要学会使用Github啊。。。方便的话帮我点几个star也好哈。

###个人github: http://github.com/icodeu

###CSDN博客:http://blog.csdn.net/icodeyou

###个人微信号:qqwanghuan 只为技术交流

image