计算机网络报告-rdt3.0

计算机网络报告-rdt3.0

在我的学习博客 可靠数据传输原理 rdt3.0 中,我已经详细介绍了 rdt3.0 的发送方和接收方的有限状态机,并且介绍了 GBN 和 SR 的原理。

报文类

我们把packet 抽象出来单独写一个类。在这个类中,我们提供了一些方法——生成包的方法生成和检验校验和的方法,解析报文的方法,模拟包报文出错的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.company; 
import java.net.*;
import java.io.*;

public class packet {
// 类中必须囊括序号、以及检验和
Integer seqNo;
Integer checksum;
// 发送的内容
public String content;

// 初始化的packet 的seqNo=1,
public packet() {
seqNo = 1;
}

// 这个方法教给发送者调用,可以将读取的信息封装到 packet当中
public void createPacket(String pContent,int seqNo) {
content = pContent;
// 为内容设置校验和
checksum = generateChecksum(content);
// 在rdt3.0中,序号只可能是0或者1,由发送者控制序号的变化
this.seqNo = seqNo;
}
// 帮助发送者生成统一格式的信息
public String generateMessage() {
return seqNo+ " " + checksum + " " + content;
}

// 生成检验和的函数,原本算法就是将前面3个16位相加,但是这里没有源端口和目的端口
// 因此,我们对发送的信息做一个处理,将其转换为ascii码作为检验和
public Integer generateChecksum(String s) {
int asciiInt;
int sum = 0;
for (int i = 0; i < s.length(); i++) {
asciiInt = (int) s.charAt(i);
sum = sum + asciiInt;
}
return sum;
}

// 这个方法是帮助接收者解析从套接字获取的data
public void parseMessage(String pcontent) {
String[] splited = pcontent.split("\\s+");
for (int i = 0; i < splited.length; i++) {
// 依次赋值给seqNo 和 checksum
seqNo = Integer.parseInt(splited[0]);
checksum = Integer.parseInt(splited[1]);
// 最后一个元素才是 要发送的内容
content = splited[2];
}
}

// 模拟包出错,可以将checksum+1
public void corruptChecksum() {
checksum = checksum + 1;
}

// 这个方法是帮助接收者判断包裹的信息是否出错的,也就是将计算得到的checksum和发送来的checksum作比较
public int validateMessage() {
Integer newChecksum = generateChecksum(content);
if (newChecksum.equals(checksum))
return seqNo;
//如果两者不相等,说明包损坏了,给发送者发送相反的ACK,这样发送者就知道出错了,需要重发
else {
seqNo = 1-seqNo;
return seqNo;
}
}
}

网络层搭建

要实现rdt 3.0 我们首先要搭建一个底层的网络层,作为传输信道。它的作用就是连接两个”主机端“,这里,发送者和接收者用同一主机上的两个进程来模拟。这样,发送者和接收者就可以通过”网络层”来相互传递消息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.company; 

import java.io.*;
import java.net.*;
import java.util.*;


public class network {
// 泛型 List用来存放要发送的所有信息
static List<Object> allMessages = new ArrayList<Object>();
// 用serverSocket这个类型的套接字来接入发送者和接收者
static ServerSocket serverSocket;

public static void main(String[] args) throws IOException {
int portNumber;

portNumber = 5678;

try {
serverSocket = new ServerSocket(portNumber);
System.out.println("Waiting... connect receiver");
/*
服务器需随时待命,因为不知道客户端什么时候会发来请求,此时,我们需要使用ServerSocket
ServerSocket与Socket不同,ServerSocket是等待客户端的请求,一旦获得一个连接请求,就创建一个Socket示例来与客户端进行通信。
*/
new MessageThread(serverSocket.accept()).start();
new MessageThread(serverSocket.accept()).start();
} catch (Exception e) {
System.out.println("I/O failure: " + e.getMessage());
e.printStackTrace();
}
}

将接收到的套接字传送给MessageThread类,为了模拟出 检验和出现错误、丢包的情况,我们这里需要分类来写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

public static class MessageThread extends Thread {
private Socket socket = null;
private static final String ACK2 = "ACK2";
MessageThread mt = null;
int ID = 0;

public MessageThread(Socket socket) {
this.socket = socket;
allMessages.add(this);
ID = allMessages.size() - 1;
}

public void run() {
String input = "";

try {
PrintWriter streamOut = null;
BufferedReader readerIn = null;
// 给接进来的socket创建一个PrintWriter,这样就可以直接通过println来向客户端或者服务端传信息
streamOut = new PrintWriter(socket.getOutputStream(), true);
// 给socket的输入流创建一个 BufferedReader,作为缓冲区,
readerIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));

System.out.println("Get connection from: " + socket.getRemoteSocketAddress().toString());

while ((input = readerIn.readLine()) != null) {
// 如果读入为空
if (input.equals("-1")) {
// 且这个线程为 发送者,那么就给接收者发一个-1
if (ID == 1) {
sendToOtherThread("-1");
}
break;
}
// 这是一个正则表达式,用来匹配任意空白字符的,也就是按照空白处分割message
String[] splitedMsg = input.split("\\s+");
//现在开始我们要模拟 顺利发送、丢包、包损坏这些情况
double x = Math.random();

//这里我设置了有一半的几率会顺利通过。当receiver 回传ack时,长度只有1,这里也设为通过
if (x < 0.5 || splitedMsg.length == 1) // PASS
{
if (splitedMsg[0].contains("ACK")) {
System.out.println("Receiver: " + splitedMsg[0] + ", to Sender");
}
else
System.out.println("Sender: Packet" + splitedMsg[0] +", PASS");
// 将一个端的信息发送到另外一端
sendToOtherThread(input);
}
// 如果出现了包损坏,我们的策略就是修改检验和,几率是1/4
else if (x >= 0.5 && x <= 0.75) // CORRUPT
{
// 创建一个新的packet对象
packet p = new packet();
p.parseMessage(input);
p.corruptChecksum();
System.out.println("Sender: Packet" + splitedMsg[0] + ", CORRUPT");
//将生成的数据发送给另外一端
sendToOtherThread(p.generateMessage());
}
// 有1/4 的几率会丢包,出现丢失时,不需要使用 sendToOtherThread,这里我没有用线程,因此我只能回传给Sender一个丢包信息,并等待sender重传
else {
System.out.println("Sender: Packet" + splitedMsg[0] + ", DROP");
streamOut.println(ACK2);
}
}
socket.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
// 给套接字输出流创建一个 PrintWriter,然后通过 println 将pMessage传给对应的套接字
public void send(String pMessage) {
PrintWriter streamOut = null;
try {
streamOut = new PrintWriter(socket.getOutputStream(), true);
streamOut.println(pMessage);
}
catch (IOException e) {
e.printStackTrace();
}
}

// 这个方法是将信息从一个进程发送到另一个进程的
public void sendToOtherThread(String pMessage) {
mt = (MessageThread)allMessages.get(1-ID);
// 通过上面定义的方法,将文件传送给另外一端
mt.send(pMessage);
}
}

}

发送者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.company;

import java.net.*;
import java.io.*;

import static java.lang.Thread.sleep;

public class sender {
static String hostName;
static int portNumber;
static String fileName;
int SeqNo;
// 首先设置一些必要的信息,比如端口号,主机名,需要传送的文件
public static void main(String[] args) {
hostName = "localhost";
portNumber = 5678;
fileName = "/Users/jasonxu/IdeaProjects/rdt3.0/src/com/company/message.txt";
try {
// 然后开启发送者
new sender().startSender(hostName, portNumber, fileName);
} catch (Exception e) {
System.out.println("Something falied: " + e.getMessage());
e.printStackTrace();
}
}



public void startSender(String hostName, int portNumber, String fileName) throws IOException {

Socket socket = null;
PrintWriter streamOut = null;
BufferedReader streamIn = null;

try {
// 创建套接字
socket = new Socket(hostName, portNumber);
// 创建一个套接字的输出流
streamOut = new PrintWriter(socket.getOutputStream(), true);
// 创建一个缓冲区,用来读取收到的信息
streamIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));

String ACK;

try {
// 从文件读取信息流
FileInputStream fstream = new FileInputStream(fileName);
// 将信息流转化成数据流
DataInputStream dataIn = new DataInputStream(fstream);
BufferedReader buffReader = new BufferedReader(new InputStreamReader(dataIn));

String input;

int totalSent = 0;
String message = "";
// 首先创建一个新的包
packet pak = new packet();
// 开始从 buffReader读取数据
while ((input = buffReader.readLine()) != null) {
//将信息按照空隙分开
String[] splited = input.split("\\s+");
int i = 0;
// 对每一段文字,生成一个包,按顺序发送
while (i < splited.length) {
// 创建包
pak.createPacket(splited[i],this.SeqNo);
// 给包填充 data
message = pak.generateMessage();
// 将数据推出去
streamOut.println(message);
// 从输入缓冲区读取数据
ACK = streamIn.readLine();

totalSent++;
//如果ACK的数值与发送报文的序号一致,则i++,发送下一个报文
if (validateACK(ACK, pak.seqNo)) {
i++;
//序号0和1之间转换
this.SeqNo = 1-this.SeqNo;
System.out.println("Waiting: " + ACK + ", totalsent: " + totalSent + ", " + ACK + "Successfully sent");
}
else {
// 否则就说明丢失或者损坏,需要等待重传
System.out.println("Waiting: packet"+ pak.seqNo + " CORRUPT OR LOST" + ", totalsent: " + totalSent + ", Waiting for resend");
sleep(1000);
}
}
streamOut.println(-1);
}
//全部发送完成后,关闭输入流
dataIn.close();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
} catch (UnknownHostException e) {
System.err.println("Cannot find the host: " + hostName);
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't read/write from the connection: " + e.getMessage());
System.exit(1);
} finally {
assert streamOut != null;
streamOut.close();
assert streamIn != null;
streamIn.close();
socket.close();
}
}
// 这个方法帮助发送者判断ACK号是否和序列号相等
public boolean validateACK(String ACKfromNetwork, Integer seqNo) {
String ACKN = "ACK" + seqNo.toString();
return ACKN.equals(ACKfromNetwork);
}
}

接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.company; 

import java.net.*;
import java.io.*;

public class receiver {
static String hostName;
static int portNumber;
// 首先给接收者确定一些基本信息: 主机名,端口
public static void main(String[] args) {

hostName = "localhost";
portNumber = 5678;

try {
// 启动一个接收者
new receiver().startReceiver(hostName, portNumber);
} catch (Exception e) {
System.out.println("Something falied: " + e.getMessage());
e.printStackTrace();
}
}

public void startReceiver(String hostName, int portNumber) throws IOException {

Socket socket = null;
PrintWriter streamOut = null;
BufferedReader streamIn = null;

try {
// 创建套接字、套接字输入输出流
socket = new Socket(hostName, portNumber);
streamOut = new PrintWriter(socket.getOutputStream(), true);
streamIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));

String input, output;
int totalReceived = 0;
StringBuilder msg = new StringBuilder();
System.out.println("Waiting... connect sender");

// 创建一个新的包,用来返回ack信息
packet pak = new packet();
while ((input = streamIn.readLine()) != null) {
// 如果输入为-1,说明包已经发送完毕,同时跳出循环
if (input.equals("-1")) {
streamOut.println(-1);
break;
}
// 解析收到的包中的信息
pak.parseMessage(input);
totalReceived++;
// 验证包是否正确
int tempNumber = pak.validateMessage();
output = "Waiting seqNo: " + pak.seqNo+ ", validateMessage: ACK" + tempNumber+ ", totalReceived: " + totalReceived + ", input" + input;
// 如果ack序号和seqNo不相等,说明包错了,等待重传
if(pak.validateMessage()!=tempNumber)
System.out.println("Waiting seqNo: " + pak.seqNo+ ", validateMessage: ACK" + tempNumber+", Packet"+pak.seqNo+" Corrupt,Please Resend");
else {
//包正确,那么打印、输出
msg.append(pak.content).append(" ");
System.out.println(output);
}
// 返回ACK序号
streamOut.println("ACK"+pak.validateMessage());
}
// 打印所有信息
System.out.println("Total Message: " + msg);
} catch (UnknownHostException e) {
System.err.println("Cannot find the host: " + hostName);
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't read/write from the connection: " + e.getMessage());
System.exit(1);
} finally {
streamOut.close();
streamIn.close();
socket.close();
}
}
}

结果:

network

这是运输通道的打印信息,我们看到,第一个packet0是顺利通过的,第二个packet1的第一次发送出现了包错误,因此返回了一个ACK0, 重发后通过。第三个包出现了两次丢包和两次出错,最后通过。

receiver

这是接收者的打印信息,我们看到出现丢包的话,接收者是收不到的,但是如果出现包错误,那么接收者会返回一个不同的ACK序号通知发送者重发

sender

这是发送者的打印信息,如果出现丢包或者包错误,会等待后重新发送,同时记录发送包的次数

-------------本文结束,感谢您的阅读-------------