一、實(shí)驗(yàn)?zāi)康?/h1>
本次實(shí)驗(yàn)中哭靖,我們通過Java語言編寫一個(gè)簡(jiǎn)單的聊天室客戶端程序具垫,實(shí)現(xiàn)多用戶群聊的功能。本次實(shí)驗(yàn)的基礎(chǔ)是基于TCP協(xié)議的以套接字(Socket)為端點(diǎn)的端到端通信技術(shù)试幽。其中做修,通信的一端是我們將要編寫的聊天室客戶端進(jìn)程,另一端是服務(wù)端進(jìn)程抡草。
二饰及、用戶界面設(shè)計(jì)
按照如下的設(shè)計(jì)來實(shí)現(xiàn)聊天室客戶端程序的用戶界面:
- 窗口的最上方是將要連接的服務(wù)器端地址和端口號(hào)。填寫后點(diǎn)擊“連接”按鈕發(fā)起連接
- 連接成功與否康震,都彈出對(duì)話框?qū)τ脩暨M(jìn)行提示
- 連接成功后燎含,按鈕文字變成“斷開”,點(diǎn)擊后斷開與服務(wù)器的連接腿短,退出聊天屏箍。
- 窗口的中間區(qū)域顯示聊天室中當(dāng)前各個(gè)用戶的聊天內(nèi)容
- 窗口最下方是用戶昵稱,以及聊天內(nèi)容編輯框橘忱,點(diǎn)擊“發(fā)送”按鈕后即將聊天消息發(fā)送到服務(wù)器端赴魁。然后服務(wù)器端再將這條消息下發(fā)到其它客戶端,從而實(shí)現(xiàn)了群聊钝诚。除了了點(diǎn)擊“發(fā)送”按鈕颖御,用戶通過按下鍵盤上的回車鍵也可以觸發(fā)消息的發(fā)送
三、程序編寫
3.1 創(chuàng)建Java應(yīng)用程序工程
啟動(dòng)Eclipse集成開發(fā)環(huán)境凝颇,選擇菜單項(xiàng)“File -> New -> Java Project”潘拱,系統(tǒng)彈出類似下面的對(duì)話框:
在“Project name”一欄中填寫你的項(xiàng)目名稱疹鳄,然后點(diǎn)擊右下角的“Finish”按鈕完成創(chuàng)建。新建的項(xiàng)目出現(xiàn)在左側(cè)的“Package Explorer”視圖中:
3.2 創(chuàng)建Java代碼包
右鍵點(diǎn)擊“src”文件夾芦岂,在彈出菜單中選擇“New -> Package”瘪弓,在彈出的對(duì)話框中填寫包名:
點(diǎn)擊“Finish”按鈕,“src”目錄下出現(xiàn)我們新建的包名:
3.3 創(chuàng)建UI視圖類
3.3.1 建立ClientView類
UI視圖類用來實(shí)現(xiàn)前文的用戶界面禽最,并且響應(yīng)交互腺怯,例如輸入文字、點(diǎn)擊按鈕川无、敲擊鍵盤等呛占。
在步驟3.2中創(chuàng)建的包名上右鍵單擊鼠標(biāo),在彈出的菜單中選擇“New -> Class”舀透,在彈出的對(duì)話框中的“Name”欄中填寫類名ClientView栓票,在父類“Superclass”欄中填寫“javax.swing.JFrame”:
點(diǎn)擊“Finish”按鈕完成創(chuàng)建决左。此時(shí)愕够,在源代碼目錄中出現(xiàn)了源文件ClientView.java。雙擊打開佛猛,內(nèi)容如下:
package com.simplechat;
import javax.swing.JFrame;
public class ClientView extends JFrame {
}
3.3.2 實(shí)現(xiàn)處理交互事件的接口
由于我們視圖中將來要響應(yīng)鼠標(biāo)點(diǎn)擊惑芭、敲擊鍵盤等交互事件,需要分別實(shí)現(xiàn)對(duì)應(yīng)的接口继找。在類定義上增加需要實(shí)現(xiàn)的接口如下:
public class ClientView extends JFrame implements
ActionListener, KeyListener {
}
其中遂跟,接口ActionListener用來處理鼠標(biāo)點(diǎn)擊事件,而接口KeyListener用來處理鍵盤事件婴渡。
此時(shí)幻锁,類名ClientView下面會(huì)出現(xiàn)紅色波浪線提示出錯(cuò)。將光標(biāo)定位到類名边臼,按F2鍵哄尔,在彈出的菜單中選擇“Add unimplemented methods”:
這時(shí),開發(fā)環(huán)境會(huì)自動(dòng)向類ClientView中添加兩個(gè)接口要求實(shí)現(xiàn)的方法如下:
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
我們重點(diǎn)關(guān)注兩個(gè)方法:
- keyPressed():處理鍵盤按下事件
- actionPerformed():處理鼠標(biāo)點(diǎn)擊事件
稍后我們會(huì)在這兩個(gè)方法中編寫事件處理代碼柠并。
3.3.3 UI元素初始化
要達(dá)到前文設(shè)計(jì)的UI顯示效果岭接,需要編寫代碼添加各個(gè)窗口元素。對(duì)于需要響應(yīng)交互事件的元素臼予,還需要綁定對(duì)事件的監(jiān)聽
首先鸣戴,在類中添加如下的成員變量:
private JTextArea taChatList; // 聊天內(nèi)容區(qū)
private JTextField tfMessage; // 聊天輸入框
private JTextField tfName; // 用戶名輸入框
private JButton btnSend; // 發(fā)送按鈕
private JLabel labelNick;
private JPanel jp1, jp2;
private JScrollPane scrollPane;
private JLabel labelHost;
private JLabel labelPort;
private JTextField tfHost; // 服務(wù)器地址輸入框
private JTextField tfPort; // 服務(wù)器端口輸入框
private JButton btnConnect; // 連接/斷開服務(wù)器按鈕
以上都是是用戶界面中的UI元素對(duì)象,后面需要操作或訪問的對(duì)象已經(jīng)通過注釋來標(biāo)明用途粘拾。
接下來窄锅,編寫一個(gè)用戶界面初始化函數(shù)initView(),對(duì)各個(gè)UI元素對(duì)象分配存儲(chǔ)空間缰雇,并按照設(shè)計(jì)要求添加到視圖中酬滤。代碼如下:
private void initView() {
taChatList = new JTextArea(20, 20);
taChatList.setEditable(false);
scrollPane = new JScrollPane(taChatList);
tfMessage = new JTextField(15);
btnSend = new JButton("發(fā)送");
jp1 = new JPanel();
labelHost = new JLabel("主機(jī)地址");
tfHost = new JTextField(15);
tfHost.setText("localhost");
labelPort = new JLabel("端口號(hào)");
tfPort = new JTextField(4);
tfPort.setText("8765");
btnConnect = new JButton("連接");
jp1.add(labelHost);
jp1.add(tfHost);
jp1.add(labelPort);
jp1.add(tfPort);
jp1.add(btnConnect);
labelNick = new JLabel("昵稱:");
tfName = new JTextField(8);
jp2 = new JPanel();
jp2.add(labelNick);
jp2.add(tfName);
tfName.setText("用戶0");
jp1.setLayout(new FlowLayout(FlowLayout.CENTER));
jp2.add(tfMessage);
jp2.add(btnSend);
jp2.setLayout(new FlowLayout(FlowLayout.CENTER));
add(jp1, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
add(jp2, BorderLayout.SOUTH);
setTitle("聊天室");
setSize(500, 500);
setLocation(450, 150);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// 當(dāng)光標(biāo)定位在聊天輸入框時(shí)監(jiān)聽回車鍵按下事件
tfMessage.addKeyListener(this);
// 為發(fā)送按鈕增加鼠標(biāo)點(diǎn)擊事件監(jiān)聽
btnSend.addActionListener(this);
// 為連接按鈕增加鼠標(biāo)點(diǎn)擊事件監(jiān)聽
btnConnect.addActionListener(this);
// 當(dāng)窗口關(guān)閉時(shí)觸發(fā)
addWindowListener(new WindowAdapter() { // 窗口關(guān)閉后斷開連接
@Override
public void windowClosing(WindowEvent e) {
}
});
}
找到keyPressed()方法签餐,添加對(duì)按下回車鍵事件的處理:
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
// 發(fā)送聊天消息
}
}
找到actionPerformed()方法,添加對(duì)兩個(gè)按鈕的響應(yīng):
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getSource() == btnSend) {
// 響應(yīng)發(fā)送按鈕
} else if (e.getSource() == btnConnect) {
// 響應(yīng)連接/斷開按鈕
}
}
最后盯串,為ClientView創(chuàng)建一個(gè)構(gòu)造方法氯檐,并且在構(gòu)造方法中調(diào)用initView()方法對(duì)用戶界面進(jìn)行創(chuàng)建:
public ClientView() {
initView();
}
3.3.4 運(yùn)行程序查看主窗口
到此為止,我們的客戶端程序的UI視圖搭建完畢体捏。在ClientView類的末尾增加主函數(shù)作為程序運(yùn)行的入口:
public static void main(String[] args) {
ClientView view = new ClientView();
}
在開發(fā)環(huán)境左側(cè)Project Explorer中選中我們的項(xiàng)目冠摄,點(diǎn)右鍵在彈出菜單中選擇“Run As -> Java Application”,如果一切整成几缭,則會(huì)看到我們的應(yīng)用程序窗口:
可以看到窗口能夠展示出來了河泳,但是還沒有實(shí)現(xiàn)任何功能。接下來我們編寫一個(gè)網(wǎng)絡(luò)服務(wù)模塊來提供程序所需要的網(wǎng)絡(luò)功能年栓。
3.4 編寫網(wǎng)絡(luò)服務(wù)模塊
3.4.1 創(chuàng)建NetworkService類
我們將網(wǎng)絡(luò)相關(guān)的處理全部放到一個(gè)專門的網(wǎng)絡(luò)服務(wù)模塊中實(shí)現(xiàn)拆挥,其它模塊需要時(shí)進(jìn)行調(diào)用即可。在這里某抓,我將該類命名為NetworkService纸兔。右鍵單擊剛才創(chuàng)建的包突勇,在彈出的菜單中選擇“New -> Class”车酣。在出現(xiàn)的對(duì)話框中的“Name”欄目中填寫類名:
點(diǎn)擊“Finish”按鈕完成創(chuàng)建拗踢。此時(shí)圃泡,對(duì)應(yīng)的源代碼文件NetworkService.java將出現(xiàn)在com.simplechat包下:
雙擊NetworkService.java打開垮抗,內(nèi)容如下:
package com.simplechat;
public class NetworkService {
}
3.4.2 定義NetworkService模塊的功能
NetworkService模塊需要提供的功能包括:
- connnect():連接到服務(wù)器
- disconnect():斷開與服務(wù)器的連接
- isConnected():判斷當(dāng)前是否已經(jīng)連接到服務(wù)器
- sendMessage():發(fā)送聊天消息
因此虏束,為NetworkService類添加與這些功能相對(duì)應(yīng)的方法:
public class NetworkService {
/**
* 連接到服務(wù)器
* @param host 服務(wù)器地址
* @param port 服務(wù)器端口
*/
public void connect(String host, int port) {
}
/**
* 斷開連接
*/
public void disconnect() {
}
/**
* 是否已經(jīng)連接到服務(wù)器
* @return true為已連接洪添,false為未連接
*/
public boolean isConnected() {
}
/**
* 發(fā)送聊天消息
* @param name 用戶名
* @param msg 消息內(nèi)容
*/
public void sendMessage(String name, String msg) {
}
}
3.4.3 定義回調(diào)接口
同時(shí)绅项,以上網(wǎng)絡(luò)功能操作完成后曲尸,往往需要反饋一些狀態(tài)赋续,例如在收到消息后通知用戶界面刷新聊天內(nèi)容,使得用戶能夠看到新消息另患。而根據(jù)分層設(shè)計(jì)的原理纽乱,這樣的代碼放在專注于網(wǎng)絡(luò)操作的NetworkService類中是非常丑陋的做法。因此柴淘,我們定義一個(gè)回調(diào)接口迫淹,其中定義各項(xiàng)處理結(jié)束的通知函數(shù)。稍后我們?cè)谟脩艚缑骖悾碈lientView類)中實(shí)現(xiàn)這個(gè)接口为严,就可以實(shí)現(xiàn)UI對(duì)網(wǎng)絡(luò)處理的響應(yīng)了敛熬。
在NetworkService類的最前面中添加接口Callback的定義,同時(shí)用Callback類型定義一個(gè)成員變量第股,并創(chuàng)建setter方法:
public class NetworkService {
public interface Callback {
void onConnected(String host, int port); //連接成功
void onConnectFailed(String host, int port); //連接失敗
void onDisconnected(); //已經(jīng)斷開連接
void onMessageSent(String name, String msg); //消息已經(jīng)發(fā)出
void onMessageReceived(String name, String msg);//收到消息
}
private Callback callback;
public void setCallback(Callback callback) {
this.callback = callback;
}
...
}
3.4.4 添加網(wǎng)絡(luò)通信相關(guān)的成員變量
添加網(wǎng)絡(luò)通信所需要的以下成員變量:
// 套接字對(duì)象
private Socket socket = null;
// 套接字輸入流對(duì)象应民,從這里讀取收到的消息
private DataInputStream inputStream = null;
// 套接字輸出流對(duì)象,從這里發(fā)送聊天消息
private DataOutputStream outputStream = null;
// 當(dāng)前連接狀態(tài)的標(biāo)記變量
private boolean isConnected = false;
3.4.5 實(shí)現(xiàn)connect()操作
connect()方法實(shí)現(xiàn)連接服務(wù)器的操作。它的邏輯如下:
- 根據(jù)參數(shù)提供的服務(wù)器地址和端口創(chuàng)建套接字诲锹。創(chuàng)建套接字的過程即建立連接的過程繁仁。
- 如果創(chuàng)建成功,記錄已連接狀態(tài)归园,同時(shí)通過回調(diào)函數(shù)通知外界連接成功黄虱。同時(shí)還要啟動(dòng)一個(gè)線程來監(jiān)聽是否有服務(wù)器發(fā)來的聊天消息。
- 如果創(chuàng)建套件字失敗庸诱,則記錄未連接狀態(tài)捻浦,通過回調(diào)函數(shù)通知外界連接失敗
為connect()方法編寫代碼如下:
public void connect(String host, int port) {
try {
// 創(chuàng)建套接字對(duì)象,與服務(wù)器建立連接
socket = new Socket(host, port);
isConnected = true;
// 通知外界已連接
if (callback != null) {
callback.onConnected(host, port);
}
// 開始偵聽是否有聊天消息到來
beginListening();
} catch (IOException e) {
// 連接服務(wù)器失敗
isConnected = false;
// 通知外界連接失敗
if (callback != null) {
callback.onConnectFailed(host, port);
}
e.printStackTrace();
}
}
其中桥爽,用來監(jiān)聽聊天記錄到來的方法beginListening()實(shí)現(xiàn)如下:
private void beginListening() {
Runnable listening = new Runnable() {
@Override
public void run() {
try {
inputStream = new DataInputStream(socket.getInputStream());
while (true) {
String[] s = inputStream.readUTF().split("#");
if (callback != null) {
callback.onMessageReceived(s[0], s[1]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
(new Thread(listening)).start();
}
3.4.6 實(shí)現(xiàn)disconnect()操作
disconnect()的功能是斷開與服務(wù)器的連接朱灿。在這里要關(guān)閉套接字,并且關(guān)閉所有的輸入钠四、輸出流盗扒。實(shí)現(xiàn)代碼如下:
public void disconnect() {
try {
if (socket != null) {
socket.close();
}
if (inputStream!= null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
isConnected = false;
// 通知外界連接斷開
if (callback != null) {
callback.onDisconnected();
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.4.6 實(shí)現(xiàn)isConnected()
外界通過調(diào)用isConnected()方法來獲知當(dāng)前是否已經(jīng)連接到服務(wù)器。簡(jiǎn)單的返回isConnected變量即可:
public boolean isConnected() {
return isConnected;
}
3.4.7 實(shí)現(xiàn)sendMessage()操作
sendMessage()方法將參數(shù)傳來的用戶名和消息串按照一定的格式發(fā)送出去缀去。操作的實(shí)質(zhì)就是將消息寫入到套接字對(duì)象的輸出流侣灶。實(shí)現(xiàn)如下:
public void sendMessage(String name, String msg) {
// 檢查參數(shù)合法性
if (name == null || "".equals(name) || msg == null || "".equals(msg)) {
return;
}
if (socket == null) { //套接字對(duì)象必須已創(chuàng)建
return;
}
try {
// 將消息寫入套接字的輸出流
outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.writeUTF(name + "#" + msg);
outputStream.flush();
// 通知外界消息已發(fā)送
if (callback != null) {
callback.onMessageSent(name, msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
到此為止,我們的網(wǎng)絡(luò)服務(wù)模塊就編寫完成了朵耕。下面炫隶,我們回到用戶界面視圖淋叶,即ClientView類中阎曹,創(chuàng)建網(wǎng)絡(luò)服務(wù)模塊對(duì)象,然后在相關(guān)的地方調(diào)用網(wǎng)絡(luò)服務(wù)模塊提供的功能煞檩。
3.5 用戶界面與網(wǎng)絡(luò)服務(wù)模塊對(duì)接
3.5.1 為ClientView類增加NetworkService模塊并實(shí)現(xiàn)回調(diào)
重新打開ClientView.java文件進(jìn)行修改处嫌。
為了在ClientView類中對(duì)網(wǎng)絡(luò)服務(wù)模塊即NetworkService類進(jìn)行調(diào)用,首先必須在ClientView類中增加一個(gè)NetworkService類型的成員:
private NetworkService networkService;
然后專門寫一個(gè)函數(shù)initNetworkService()來初始化這個(gè)networkService對(duì)象斟湃。這個(gè)函數(shù)主要做兩件事:
- 創(chuàng)建networkService對(duì)象(new)
- 設(shè)置回調(diào)接口以處理NetworkService中各個(gè)網(wǎng)絡(luò)操作發(fā)來的通知
initNetworkService()函數(shù)實(shí)現(xiàn)如下:
private void initNetworkService() {
networkService = new NetworkService();
networkService.setCallback(new Callback() {
@Override
public void onConnected(String host, int port) {
// 連接成功時(shí)熏迹,彈對(duì)話框提示,并將按鈕文字改為“斷開”
alert("連接", "成功連接到[" + host + ":" + port + "]");
btnConnect.setText("斷開");
}
@Override
public void onConnectFailed(String host, int port) {
// 連接失敗時(shí)凝赛,彈對(duì)話框提示注暗,并將按鈕文字設(shè)為“連接”
alert("連接", "無法連接到[" + host + ":" + port + "]");
btnConnect.setText("連接");
}
@Override
public void onDisconnected() {
// 斷開連接時(shí),彈對(duì)話框提示墓猎,并將按鈕文字設(shè)為“連接”
alert("連接", "連接已斷開");
btnConnect.setText("連接");
}
@Override
public void onMessageSent(String name, String msg) {
// 發(fā)出消息時(shí)捆昏,清空消息輸入框,并將消息顯示在消息區(qū)
tfMessage.setText("");
taChatList.append("我(" + name + "):\r\n" + msg + "\r\n");
}
@Override
public void onMessageReceived(String name, String msg) {
// 收到消息時(shí)毙沾,將消息顯示在消息區(qū)
taChatList.append(name + ":\r\n" + msg + "\r\n");
}
});
}
其中骗卜,alert()函數(shù)用來顯示一個(gè)對(duì)話框以向用戶通告某個(gè)信息,實(shí)現(xiàn)如下:
// 顯示標(biāo)題為title,內(nèi)容為message的對(duì)話框
private void alert(String title, String message) {
JOptionPane.showMessageDialog(this, message, title, JOptionPane.INFORMATION_MESSAGE);
}
然后找到構(gòu)造方法ClientView()寇仓,在其末尾調(diào)用這個(gè)函數(shù)如下:
public ClientView() {
initView();
initNetworkService();
}
3.5.2 實(shí)現(xiàn)用戶交互
不同的用戶交互將會(huì)觸發(fā)相應(yīng)的網(wǎng)絡(luò)操作举户,包括:
- 未連接狀態(tài)下,點(diǎn)擊連接/斷開按鈕執(zhí)行連接操作
- 已連接狀態(tài)下遍烦,點(diǎn)擊連接/斷開按鈕執(zhí)行斷開連接操作
- 已連接狀態(tài)下俭嘁,關(guān)閉窗口執(zhí)行斷開連接操作
- 按回車鍵發(fā)送消息
下面分別調(diào)用NetworkService模塊提供的功能來完成以上的交互操作。
3.5.2.1 處理關(guān)閉窗口操作
在ClientView類中找到如下代碼:
// 當(dāng)窗口關(guān)閉時(shí)觸發(fā)
addWindowListener(new WindowAdapter() { // 窗口關(guān)閉后斷開連接
@Override
public void windowClosing(WindowEvent e) {
}
});
增加斷開連接操作服猪,如下:
// 當(dāng)窗口關(guān)閉時(shí)觸發(fā)
addWindowListener(new WindowAdapter() { // 窗口關(guān)閉后斷開連接
@Override
public void windowClosing(WindowEvent e) {
networkService.disconnect();
}
});
3.5.2.2 處理按鈕點(diǎn)擊操作
其中包括對(duì)連接/斷開按鈕的點(diǎn)擊兄淫,以及對(duì)發(fā)送按鈕的點(diǎn)擊。前者連接或者斷開服務(wù)器蔓姚,后者將編輯框中的消息發(fā)送出去捕虽。
找到actionPerformed()方法,改寫如下:
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getSource() == btnSend) {
sendMessage();
} else if (e.getSource() == btnConnect) {
// 響應(yīng)連接/斷開按鈕
if (!networkService.isConnected()) {
// 未連接狀態(tài)下坡脐,執(zhí)行連接服務(wù)器操作
String host = tfHost.getText();
int port = Integer.valueOf(tfPort.getText());
networkService.connect(host, port);
} else {
// 已連接狀態(tài)下泄私,執(zhí)行斷開連接操作
networkService.disconnect();
}
}
}
其中,sendMessage()方法實(shí)現(xiàn)如下:
private void sendMessage() {
// 響應(yīng)發(fā)送按鈕
String name = tfName.getText();
String msg = tfMessage.getText();
// 檢查參數(shù)合法性
if (name == null || msg == null || "".equals(name) || "".equals(msg)) {
return;
}
// 發(fā)送消息
networkService.sendMessage(name, msg);
}
3.5.2.3 處理回車鍵按下操作
當(dāng)按下回車鍵時(shí)备闲,如果聊天輸入框中有內(nèi)容晌端,就將其發(fā)送出去。
找到keyPressed()方法恬砂,改下如下:
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
// 發(fā)送聊天消息
sendMessage();
}
}
到此為止咧纠,聊天室客戶端程序編寫完成。
四泻骤、 測(cè)試聊天室系統(tǒng)
4.1 運(yùn)行服務(wù)器端程序
從以下鏈接下載已編寫好的聊天室服務(wù)器端代碼工程:
https://pan.baidu.com/s/1GHHWY3pv5DBrIQvSBqfl2Q
下載后解壓漆羔。在eclipse中選擇菜單項(xiàng)“File -> Import”,在彈出的對(duì)話框中選擇“General -> Existing Project into Workspace”狱掂,點(diǎn)擊“Next”按鈕進(jìn)入下一步對(duì)話框演痒,在“Select Root Directory”項(xiàng)下選擇剛才解壓出來的目錄:
點(diǎn)擊“Finish”按鈕完成導(dǎo)入。此時(shí)趋惨,服務(wù)器端項(xiàng)目出現(xiàn)在窗口左側(cè)的列表中:
按照之前運(yùn)行客戶端程序同樣的方法運(yùn)行此項(xiàng)目鸟顺,將會(huì)看到如下的窗口:
點(diǎn)擊“打開服務(wù)器”按鈕,使服務(wù)器進(jìn)入運(yùn)行狀態(tài):
4.2 運(yùn)行客戶端程序并連接服務(wù)器
連續(xù)運(yùn)行兩次我們編寫的客戶端程序器虾,得到兩個(gè)客戶端程序窗口讯嫂。分別點(diǎn)擊各自的“連接按鈕”,觀察服務(wù)器程序窗口的變化兆沙。
分別在各個(gè)客戶端窗口輸入并發(fā)送消息欧芽,觀察是否能夠出現(xiàn)在另一客戶端窗口。