停止等待arq協(xié)議:
停止等待協(xié)議(stop-and-wait)是最簡單但也是最基礎(chǔ)的數(shù)據(jù)鏈路層協(xié)議黄伊。很多有關(guān)協(xié)議的基本概念都可以從這個協(xié)議中學(xué)習(xí)到还最。“停止等待”就是每發(fā)送完一個分組就停止發(fā)送扶叉,等待對方的確認(rèn)枣氧。在收到確認(rèn)后再發(fā)送下一個分組。
由于這里我們采用的是端對端的傳輸酪劫,所以我們并沒有采用tcp進(jìn)行流式套接字的傳輸,而是采用udp搪桂,進(jìn)行的數(shù)據(jù)報的傳輸。
UDP 是User Datagram Protocol的簡稱魄藕, 中文名是用戶數(shù)據(jù)報協(xié)議,是OSI(Open System Interconnection寝姿,開放式系統(tǒng)互聯(lián)) 參考模型中一種無連接的傳輸層協(xié)議埃篓,提供面向事務(wù)的簡單不可靠信息傳送服務(wù)架专,IETF RFC 768是UDP的正式規(guī)范部脚。UDP在IP報文的協(xié)議號是17
UDP協(xié)議全稱是用戶數(shù)據(jù)報協(xié)議[1] ,在網(wǎng)絡(luò)中它與TCP協(xié)議一樣用于處理數(shù)據(jù)包钱雷,是一種無連接的協(xié)議。在OSI模型中灿椅,在第四層——傳輸層操刀,處于IP協(xié)議的上一層。UDP有不提供數(shù)據(jù)包分組欢唾、組裝和不能對數(shù)據(jù)包進(jìn)行排序的缺點,也就是說杏头,當(dāng)報文發(fā)送之后醇王,是無法得知其是否安全完整到達(dá)的。UDP用來支持那些需要在計算機(jī)之間傳輸數(shù)據(jù)的網(wǎng)絡(luò)應(yīng)用滥朱。包括網(wǎng)絡(luò)視頻會議系統(tǒng)在內(nèi)的眾多的客戶/服務(wù)器模式的網(wǎng)絡(luò)應(yīng)用都需要使用UDP協(xié)議根暑。UDP協(xié)議從問世至今已經(jīng)被使用了很多年,雖然其最初的光彩已經(jīng)被一些類似協(xié)議所掩蓋徙邻,但是即使是在今天UDP仍然不失為一項非常實用和可行的網(wǎng)絡(luò)傳輸層協(xié)議排嫌。
與所熟知的TCP(傳輸控制協(xié)議)協(xié)議一樣,UDP協(xié)議直接位于IP(網(wǎng)際協(xié)議)協(xié)議的頂層缰犁。根據(jù)OSI(開放系統(tǒng)互連)參考模型淳地,UDP和TCP都屬于傳輸層協(xié)議。UDP協(xié)議的主要作用是將網(wǎng)絡(luò)數(shù)據(jù)流量壓縮成數(shù)據(jù)包的形式帅容。一個典型的數(shù)據(jù)包就是一個二進(jìn)制數(shù)據(jù)的傳輸單位蕴茴。每一個數(shù)據(jù)包的前8個字節(jié)用來包含報頭信息,剩余字節(jié)則用來包含具體的傳輸數(shù)據(jù)辫狼。
再解釋說明一下真椿,由于系統(tǒng)層面的原因锋八,會有自動校驗的措施胸竞,而且因為我做的是本機(jī)的測試腺占,出錯率也會很低尽爆,所以我們需要模擬出錯的情況募强。我們利用隨機(jī)數(shù)的算法,做了一個模擬的隨機(jī)數(shù)算法:
package UDP;
/**
* Created by linSir on 16/10/5.用來模擬傳輸過程(udp)
*/
public class ImitateTransfer {
/**
* 用來模擬傳輸過程中的各種情況
* 1.無差錯情況(60%) 狀態(tài)碼:100
* 2.出現(xiàn)差錯
* ①.接收到了玄括,做校驗時發(fā)現(xiàn)有錯(20%) 狀態(tài)碼:101
* ②.傳輸過程中丟失數(shù)據(jù)(20%) 狀態(tài)碼:102
* 3.確認(rèn)丟失和確認(rèn)遲到
* ①.確認(rèn)丟失(20%)狀態(tài)碼:103
* ②.確認(rèn)遲到(20%)狀態(tài)碼:104
*/
public static String transfer_send() {//發(fā)送數(shù)據(jù)的三種情況
int x = (int) (Math.random() * 10);//0-9
switch (x) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
return "100";
case 6:
case 7:
return "101";
case 8:
case 9:
return "102";
}
return "100";
}
public static String transfer_confirm() {//發(fā)送確認(rèn)命令的三種情況
int x = (int) (Math.random() * 10);//0-9
switch (x) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
return "100";
case 6:
case 7:
return "103";
case 8:
case 9:
return "104";
}
return "100";
}
}
這樣我們就做完了堡僻,模擬出錯的情況壤躲。下面我們看一下代碼客税,再解釋:
客戶端A(負(fù)責(zé)發(fā)送):
package UDP;
import 實驗二.ImitateTransfer;
import java.text.SimpleDateFormat;
import java.util.*;
import java.io.IOException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.net.*;
import java.util.Timer;
class Window extends JFrame {
private static JTextArea showArea;
private JTextField inputField;
private static String IPadress;
public static String[] datas = new String[1000];
public static Timer timer = new Timer();
public static int m = 0;
public static void sentData(String Message) {
byte[] dataarr = new byte[100010];
dataarr = Message.getBytes();
try {
Thread.currentThread().sleep(500);//毫秒
} catch (Exception e) {
}
try {
InetAddress sentIP = InetAddress.getByName(IPadress);
DatagramSocket dsset = new DatagramSocket(62000);
DatagramPacket dprec = new DatagramPacket(dataarr, dataarr.length, sentIP, 64650);
System.out.println(sentIP);
// 從己方62000 端口發(fā)送到客戶端64650端口
dsset.send(dprec);
dsset.close();
timer1(1);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void recieveData() {
byte[] dataarr = new byte[100010];
try {
DatagramSocket dsset = new DatagramSocket(63300);
// 監(jiān)聽己方63300端口收到的消息
DatagramPacket dprec = new DatagramPacket(dataarr, dataarr.length);
dsset.receive(dprec);
dsset.close();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append(format.format(new Date()) + "\n" + "新消息: " + new String(dataarr).trim() + "\n");
String content2 = new String(dataarr).trim().split("\\(")[1].split("\\)")[0];
System.out.println(content2);
switch (content2) {
case "成功接受":
timer1(0);
m++;
String type = "";
type = ImitateTransfer.transfer_send();
switch (type) {
case "100":
type = "成功發(fā)送";
break;
case "101":
type = "做校驗時出錯";
break;
case "102":
type = "傳送時丟失";
break;
}
sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (正常發(fā)送)");
showArea.append("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (正常發(fā)送)" + "\n");
break;
case "確認(rèn)丟失":
break;
case "確認(rèn)超時":
break;
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void startChat() {
for (int i = 0; i < datas.length; i++) {
datas[i] = String.valueOf(i);
}
JLabel displyjLabel = new JLabel("客戶端A");
displyjLabel.setBounds(150, 10, 100, 15);
JPanel myJPanel = new JPanel();
myJPanel.setLayout(null);
this.setContentPane(myJPanel);
myJPanel.add(displyjLabel);
showArea = new JTextArea();
showArea.setLineWrap(true);
JScrollPane scrollpane = new JScrollPane(showArea);
scrollpane.setBounds(20, 30, 350, 350);
scrollpane.setBorder(new BevelBorder(BevelBorder.LOWERED, Color.CYAN, Color.BLUE, null, null));
scrollpane.setVisible(true);
inputField = new JTextField();
inputField.setBounds(20, 410, 280, 25);
inputField.setVisible(true);
myJPanel.add(scrollpane);
JButton mybutton = new JButton("發(fā)送");
mybutton.setBounds(310, 410, 60, 25);
myJPanel.add(mybutton);
myJPanel.add(inputField);
myJPanel.setVisible(true);
JButton quitjButton = new JButton("退出");
quitjButton.setBounds(250, 450, 100, 30);
myJPanel.add(quitjButton);
quitjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.exit(0);
}
});
JButton returnjButton = new JButton("返回主界面");
returnjButton.setBounds(20, 450, 100, 30);
myJPanel.add(returnjButton);
returnjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
initWindow();
}
});
this.setVisible(true);
inputField.requestFocus();
inputField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyChar() == '\n') {
String type = "";
type = ImitateTransfer.transfer_send();
switch (type) {
case "100":
type = "成功發(fā)送";
break;
case "101":
type = "做校驗時出錯";
break;
case "102":
type = "傳送時丟失";
break;
}
//A發(fā)送: 數(shù)據(jù)為:1 類型為:成功發(fā)送; (開始發(fā)送)
sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (開始發(fā)送)");
inputField.setText("");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (開始發(fā)送)" + "\n");
//sendMessage("A(發(fā)送)-" + datas[m] + "-" + 實驗二.ImitateTransfer.transfer_send() + "---開始發(fā)送");
}
}
});
mybutton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// 當(dāng)發(fā)送鍵被按下
String type = "";
type = ImitateTransfer.transfer_send();
switch (type) {
case "100":
type = "成功發(fā)送";
break;
case "101":
type = "做校驗時出錯";
break;
case "102":
type = "傳送時丟失";
break;
}
//A發(fā)送: 數(shù)據(jù)為:1 類型為:成功發(fā)送; (開始發(fā)送)
sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (開始發(fā)送)");
inputField.setText("");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (開始發(fā)送)" + "\n");
//sendMessage("A(發(fā)送)-" + datas[m] + "-" + 實驗二.ImitateTransfer.transfer_send() + "---開始發(fā)送");
}
});
}
public void initWindow() {
JPanel myjPanel = new JPanel();
myjPanel.setLayout(null);
this.setContentPane(myjPanel);
JLabel myjLabel = new JLabel("歡迎使用本聊天程序");
myjLabel.setBounds(50, 100, 300, 40);
myjLabel.setForeground(Color.cyan);
myjLabel.setFont(new Font("HirakakuProN-W6", Font.BOLD, 30));
JLabel tishiJLabel = new JLabel("請輸入對方的IP地址:");
tishiJLabel.setBounds(15, 300, 150, 20);
final JTextField ipJTextField = new JTextField("127.128.0.1");
ipJTextField.setBounds(150, 300, 115, 20);
JButton okJButton = new JButton("確定");
okJButton.setBounds(280, 300, 70, 20);
ipJTextField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyChar() == '\n') {
startChat();
}
}
});
okJButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
IPadress = ipJTextField.getText();
startChat();
}
});
myjPanel.add(tishiJLabel);
myjPanel.add(myjLabel);
myjPanel.add(ipJTextField);
myjPanel.add(okJButton);
JButton quitjButton = new JButton("退出");
quitjButton.setBounds(150, 350, 100, 30);
myjPanel.add(quitjButton);
quitjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.exit(0);
}
});
this.setVisible(true);
}
public Window() {
this.setBounds(420, 100, 400, 550);
this.setLayout(null);
this.setTitle("客戶端A");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initWindow();
while (true) {
recieveData();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Window();
}
public static void timer1(int q) {
if (q == 1) {
timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
String type = "";
type = ImitateTransfer.transfer_send();
switch (type) {
case "100":
type = "成功發(fā)送";
break;
case "101":
type = "做校驗時出錯";
break;
case "102":
type = "傳送時丟失";
break;
}
sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (超時重發(fā))");
showArea.append("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (超時重發(fā))" + "\n");
}
}, 2000);
}
if (q == 0) {
timer.cancel();
}
}
}
客戶端B(負(fù)責(zé)接收):
package UDP;
import java.text.SimpleDateFormat;
import java.util.*;
import java.io.IOException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.net.*;
class Window2 extends JFrame {
private JTextArea showArea;
private JTextField inputField;
private String IPadress;
public void sentData(String Message) {
byte[] dataarr = new byte[100010];
try {
Thread.currentThread().sleep(500);//毫秒
} catch (Exception e) {
}
dataarr = Message.getBytes();
try {
InetAddress sentIP = InetAddress.getByName(IPadress);
DatagramSocket dsset = new DatagramSocket(60010);
DatagramPacket dprec = new DatagramPacket(dataarr, dataarr.length, sentIP, 63300);
// 從己方60010端口發(fā)送到對方63300端口
dsset.send(dprec);
dsset.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void recieveData() {
byte[] dataarr = new byte[100010];
try {
DatagramSocket dsset = new DatagramSocket(64650);
DatagramPacket dprec = new DatagramPacket(dataarr, dataarr.length);
dsset.receive(dprec);
dsset.close();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append(format.format(new Date()) + "\n" + "新消息: " + new String(dataarr).trim() + "\n");
String a_type = new String(dataarr).trim().split(":")[3].split(";")[0];
String content = new String(dataarr).trim().split(":")[2].split("類")[0];
//sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (開始發(fā)送)");
String type = "";
type = ImitateTransfer.transfer_confirm();
switch (type) {
case "100":
type = "成功接受";
break;
case "103":
type = "確認(rèn)丟失";
break;
case "104":
type = "確認(rèn)超時";
break;
}
switch (a_type) {
case "成功發(fā)送":
sentData("B發(fā)送:" + "收到的數(shù)據(jù)為:" + content + " 類型(" + type + ") ");
break;
case "做校驗時出錯":
break;
case "傳送時丟失":
break;
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void startChat() {
JLabel displyjLabel = new JLabel("客戶端B");
displyjLabel.setBounds(150, 10, 100, 15);
JPanel myJPanel = new JPanel();
myJPanel.setLayout(null);
this.setContentPane(myJPanel);
myJPanel.add(displyjLabel);
showArea = new JTextArea();
showArea.setLineWrap(true);
JScrollPane scrollpane = new JScrollPane(showArea);
scrollpane.setBounds(20, 30, 350, 350);
scrollpane.setBorder(new BevelBorder(BevelBorder.LOWERED, Color.CYAN, Color.BLUE, null, null));
scrollpane.setVisible(true);
inputField = new JTextField();
inputField.setBounds(20, 410, 280, 25);
inputField.setVisible(true);
myJPanel.add(scrollpane);
JButton mybutton = new JButton("發(fā)送");
mybutton.setBounds(310, 410, 60, 25);
myJPanel.add(mybutton);
myJPanel.add(inputField);
myJPanel.setVisible(true);
JButton quitjButton = new JButton("退出");
quitjButton.setBounds(250, 450, 100, 30);
myJPanel.add(quitjButton);
quitjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.exit(0);
}
});
JButton returnjButton = new JButton("返回主界面");
returnjButton.setBounds(20, 450, 100, 30);
myJPanel.add(returnjButton);
returnjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
initWindow();
}
});
this.setVisible(true);
inputField.requestFocus();
inputField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyChar() == '\n') {
String Message = inputField.getText().trim();
sentData(Message);
inputField.setText("");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append(format.format(new Date()) + "\n" + "我發(fā)送: " + Message + "\n");
}
}
});
mybutton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// 當(dāng)發(fā)送鍵被按下
String Message = inputField.getText().trim();
sentData(Message);
inputField.setText("");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
showArea.append(format.format(new Date()) + "\n" + "我發(fā)送: " + Message + "\n");
}
});
}
public void initWindow() {
JPanel myjPanel = new JPanel();
myjPanel.setLayout(null);
this.setContentPane(myjPanel);
JLabel myjLabel = new JLabel("歡迎使用本聊天程序");
myjLabel.setBounds(50, 100, 300, 40);
myjLabel.setForeground(Color.cyan);
myjLabel.setFont(new Font("HirakakuProN-W6", Font.BOLD, 30));
JLabel tishiJLabel = new JLabel("請輸入對方的IP地址:");
tishiJLabel.setBounds(15, 300, 150, 20);
final JTextField ipJTextField = new JTextField("127.128.0.1");
ipJTextField.setBounds(150, 300, 115, 20);
JButton okJButton = new JButton("確定");
okJButton.setBounds(280, 300, 70, 20);
ipJTextField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyChar() == '\n') {
startChat();
}
}
});
okJButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
IPadress = ipJTextField.getText();
startChat();
}
});
myjPanel.add(tishiJLabel);
myjPanel.add(myjLabel);
myjPanel.add(ipJTextField);
myjPanel.add(okJButton);
JButton quitjButton = new JButton("退出");
quitjButton.setBounds(150, 350, 100, 30);
myjPanel.add(quitjButton);
quitjButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.exit(0);
}
});
this.setVisible(true);
}
public Window2() {
this.setBounds(420, 100, 400, 550);
this.setLayout(null);
this.setTitle("客戶端B");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initWindow();
while (true) {
recieveData();
System.out.println("-----");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Window2();
}
}
相信,有很多人贬养,已經(jīng)看懂了误算,這個程序了,先聲明一下咖杂,本程序是我在上網(wǎng)找的蚊夫,只是修改了一點點知纷。
這個程序說的就是琅轧,一個java的可視化界面+一個發(fā)送數(shù)據(jù)的函數(shù)+一個接收數(shù)據(jù)的函數(shù)乍桂,無論發(fā)送還是接收权谁,我都做了500毫秒的延遲闯传,用來模擬在路上的時間字币,也都需要指定ip和端口號洗出,就可以從這個端口發(fā)送到對方的端口了图谷,對方只需要監(jiān)視自己的端口就可以了翩活。
然后便贵,在發(fā)送數(shù)據(jù)的過程中,加了一個利用隨機(jī)數(shù)產(chǎn)生的承璃。
發(fā)送的類型,有三種盔粹,分別是:1.成功數(shù)據(jù)發(fā)送 2.發(fā)送的數(shù)據(jù)做校驗會出錯 3.發(fā)送的數(shù)據(jù)超時 隘梨。
接收的類型也有三種舷嗡,分別是:1.發(fā)送確認(rèn)成功 2.發(fā)送確認(rèn)超時
3.發(fā)送確認(rèn)丟失
當(dāng)A向B發(fā)送數(shù)據(jù)時进萄,B會判斷,如果是出錯或者超時兜蠕,B不會做任何處理扰肌,這個時候便會觸發(fā)A的超時計時器,重傳晶府。
當(dāng)B向A發(fā)送確認(rèn)時桂躏,如果確認(rèn)超時,或者確認(rèn)丟失川陆,A都會重新向B發(fā)送正確的數(shù)據(jù)剂习。
如果A接收到B的一次成功接收的指令,就會將數(shù)據(jù)向下滾動一位,傳一下數(shù)據(jù)鳞绕。
超時定時器(函數(shù)timer1)也很好理解失仁,只是讓它兩秒的等待,然后去重新發(fā)送數(shù)據(jù)们何,如果在兩秒內(nèi)萄焦,收到了確認(rèn)了,成功接收的指令冤竹,就會關(guān)閉這個超時定時器拂封。并且再向B發(fā)送一個數(shù)據(jù),同時再打開超時計時器就可以了鹦蠕。
當(dāng)然冒签,操作也非常簡單,只需要在钟病,客戶端A的輸入窗口萧恕,敲一下回車,或者點擊一下發(fā)送就行档悠,內(nèi)容是自動填充的廊鸥,不用手動輸入望浩。
效果圖:
大概這個模擬的過程就是這個樣子辖所,當(dāng)然其中肯定有很多,代碼規(guī)范磨德,代碼格式缘回,代碼復(fù)用做的很不好的地方~ 歡迎大家提意見,我都會改正的~