停止等待arq協(xié)議:
停止等待協(xié)議(stop-and-wait)是最簡(jiǎn)單但也是最基礎(chǔ)的數(shù)據(jù)鏈路層協(xié)議。很多有關(guān)協(xié)議的基本概念都可以從這個(gè)協(xié)議中學(xué)習(xí)到?!巴V沟却本褪敲堪l(fā)送完一個(gè)分組就停止發(fā)送,等待對(duì)方的確認(rèn)。在收到確認(rèn)后再發(fā)送下一個(gè)分組。
由于這里我們采用的是端對(duì)端的傳輸,所以我們并沒(méi)有采用tcp進(jìn)行流式套接字的傳輸,而是采用udp,進(jìn)行的數(shù)據(jù)報(bào)的傳輸。
UDP 是User Datagram Protocol的簡(jiǎn)稱, 中文名是用戶數(shù)據(jù)報(bào)協(xié)議,是OSI(Open System Interconnection,開放式系統(tǒng)互聯(lián)) 參考模型中一種無(wú)連接的傳輸層協(xié)議,提供面向事務(wù)的簡(jiǎn)單不可靠信息傳送服務(wù),IETF RFC 768是UDP的正式規(guī)范。UDP在IP報(bào)文的協(xié)議號(hào)是17
UDP協(xié)議全稱是用戶數(shù)據(jù)報(bào)協(xié)議[1] ,在網(wǎng)絡(luò)中它與TCP協(xié)議一樣用于處理數(shù)據(jù)包,是一種無(wú)連接的協(xié)議。在OSI模型中,在第四層——傳輸層,處于IP協(xié)議的上一層。UDP有不提供數(shù)據(jù)包分組、組裝和不能對(duì)數(shù)據(jù)包進(jìn)行排序的缺點(diǎn),也就是說(shuō),當(dāng)報(bào)文發(fā)送之后,是無(wú)法得知其是否安全完整到達(dá)的。UDP用來(lái)支持那些需要在計(jì)算機(jī)之間傳輸數(shù)據(jù)的網(wǎng)絡(luò)應(yīng)用。包括網(wǎng)絡(luò)視頻會(huì)議系統(tǒng)在內(nèi)的眾多的客戶/服務(wù)器模式的網(wǎng)絡(luò)應(yīng)用都需要使用UDP協(xié)議。UDP協(xié)議從問(wèn)世至今已經(jīng)被使用了很多年,雖然其最初的光彩已經(jīng)被一些類似協(xié)議所掩蓋,但是即使是在今天UDP仍然不失為一項(xiàng)非常實(shí)用和可行的網(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ù)包的形式。一個(gè)典型的數(shù)據(jù)包就是一個(gè)二進(jìn)制數(shù)據(jù)的傳輸單位。每一個(gè)數(shù)據(jù)包的前8個(gè)字節(jié)用來(lái)包含報(bào)頭信息,剩余字節(jié)則用來(lái)包含具體的傳輸數(shù)據(jù)。
再解釋說(shuō)明一下,由于系統(tǒng)層面的原因,會(huì)有自動(dòng)校驗(yàn)的措施,而且因?yàn)槲易龅氖潜緳C(jī)的測(cè)試,出錯(cuò)率也會(huì)很低,所以我們需要模擬出錯(cuò)的情況。我們利用隨機(jī)數(shù)的算法,做了一個(gè)模擬的隨機(jī)數(shù)算法:
package UDP;
/**
* Created by linSir on 16/10/5.用來(lái)模擬傳輸過(guò)程(udp)
*/
public class ImitateTransfer {
/**
* 用來(lái)模擬傳輸過(guò)程中的各種情況
* 1.無(wú)差錯(cuò)情況(60%) 狀態(tài)碼:100
* 2.出現(xiàn)差錯(cuò)
* ①.接收到了,做校驗(yàn)時(shí)發(fā)現(xiàn)有錯(cuò)(20%) 狀態(tài)碼:101
* ②.傳輸過(guò)程中丟失數(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";
}
}
這樣我們就做完了,模擬出錯(cuò)的情況。下面我們看一下代碼,再解釋:
客戶端A(負(fù)責(zé)發(fā)送):
package UDP;
import 實(shí)驗(yàn)二.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 = "做校驗(yàn)時(shí)出錯(cuò)";
break;
case "102":
type = "傳送時(shí)丟失";
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)超時(shí)":
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 = "做校驗(yàn)時(shí)出錯(cuò)";
break;
case "102":
type = "傳送時(shí)丟失";
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] + "-" + 實(shí)驗(yàn)二.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 = "做校驗(yàn)時(shí)出錯(cuò)";
break;
case "102":
type = "傳送時(shí)丟失";
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] + "-" + 實(shí)驗(yàn)二.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("請(qǐng)輸入對(duì)方的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 = "做校驗(yàn)時(shí)出錯(cuò)";
break;
case "102":
type = "傳送時(shí)丟失";
break;
}
sentData("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (超時(shí)重發(fā))");
showArea.append("A發(fā)送: 數(shù)據(jù)為:" + datas[m] + "類型為:" + type + "; (超時(shí)重發(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ā)送到對(duì)方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)超時(shí)";
break;
}
switch (a_type) {
case "成功發(fā)送":
sentData("B發(fā)送:" + "收到的數(shù)據(jù)為:" + content + " 類型(" + type + ") ");
break;
case "做校驗(yàn)時(shí)出錯(cuò)":
break;
case "傳送時(shí)丟失":
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("請(qǐng)輸入對(duì)方的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)看懂了,這個(gè)程序了,先聲明一下,本程序是我在上網(wǎng)找的,只是修改了一點(diǎn)點(diǎn)。
這個(gè)程序說(shuō)的就是,一個(gè)java的可視化界面+一個(gè)發(fā)送數(shù)據(jù)的函數(shù)+一個(gè)接收數(shù)據(jù)的函數(shù),無(wú)論發(fā)送還是接收,我都做了500毫秒的延遲,用來(lái)模擬在路上的時(shí)間,也都需要指定ip和端口號(hào),就可以從這個(gè)端口發(fā)送到對(duì)方的端口了,對(duì)方只需要監(jiān)視自己的端口就可以了。
然后,在發(fā)送數(shù)據(jù)的過(guò)程中,加了一個(gè)利用隨機(jī)數(shù)產(chǎn)生的。
發(fā)送的類型,有三種,分別是:1.成功數(shù)據(jù)發(fā)送 2.發(fā)送的數(shù)據(jù)做校驗(yàn)會(huì)出錯(cuò) 3.發(fā)送的數(shù)據(jù)超時(shí) 。
接收的類型也有三種,分別是:1.發(fā)送確認(rèn)成功 2.發(fā)送確認(rèn)超時(shí)
3.發(fā)送確認(rèn)丟失
當(dāng)A向B發(fā)送數(shù)據(jù)時(shí),B會(huì)判斷,如果是出錯(cuò)或者超時(shí),B不會(huì)做任何處理,這個(gè)時(shí)候便會(huì)觸發(fā)A的超時(shí)計(jì)時(shí)器,重傳。
當(dāng)B向A發(fā)送確認(rèn)時(shí),如果確認(rèn)超時(shí),或者確認(rèn)丟失,A都會(huì)重新向B發(fā)送正確的數(shù)據(jù)。
如果A接收到B的一次成功接收的指令,就會(huì)將數(shù)據(jù)向下滾動(dòng)一位,傳一下數(shù)據(jù)。
超時(shí)定時(shí)器(函數(shù)timer1)也很好理解,只是讓它兩秒的等待,然后去重新發(fā)送數(shù)據(jù),如果在兩秒內(nèi),收到了確認(rèn)了,成功接收的指令,就會(huì)關(guān)閉這個(gè)超時(shí)定時(shí)器。并且再向B發(fā)送一個(gè)數(shù)據(jù),同時(shí)再打開超時(shí)計(jì)時(shí)器就可以了。
當(dāng)然,操作也非常簡(jiǎn)單,只需要在,客戶端A的輸入窗口,敲一下回車,或者點(diǎn)擊一下發(fā)送就行,內(nèi)容是自動(dòng)填充的,不用手動(dòng)輸入。
效果圖:



大概這個(gè)模擬的過(guò)程就是這個(gè)樣子,當(dāng)然其中肯定有很多,代碼規(guī)范,代碼格式,代碼復(fù)用做的很不好的地方~ 歡迎大家提意見(jiàn),我都會(huì)改正的~