【教程】基于Socket的Java聊天室客戶端程序

一、實(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ì)看到如下的窗口:

服務(wù)器端程序窗口

點(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)在另一客戶端窗口。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挤悉,一起剝皮案震驚了整個(gè)濱河市渐裸,隨后出現(xiàn)的幾起案子巫湘,更是在濱河造成了極大的恐慌昏鹃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洞渤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡载迄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門护昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惋耙,你說我怎么就攤上這事≌篱唬” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵灭美,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我届腐,道長(zhǎng),這世上最難降的妖魔是什么梯捕? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任窝撵,我火速辦了婚禮傀顾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碌奉。我一直安慰自己短曾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布赐劣。 她就那樣靜靜地躺著嫉拐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魁兼。 梳的紋絲不亂的頭發(fā)上婉徘,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼盖呼。 笑死儒鹿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的几晤。 我是一名探鬼主播约炎,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蟹瘾!你這毒婦竟也來了圾浅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤憾朴,失蹤者是張志新(化名)和其女友劉穎狸捕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體众雷,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡府寒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了报腔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片株搔。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纯蛾,靈堂內(nèi)的尸體忽然破棺而出纤房,到底是詐尸還是另有隱情,我是刑警寧澤翻诉,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站碰煌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛾派。R本人自食惡果不足惜个少,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一夜焦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巷波,春花似錦、人聲如沸抹镊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儡炼。三九已至查蓉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妹田,已是汗流浹背鹃共。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工霜浴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晌纫。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓锹漱,卻偏偏與公主長(zhǎng)得像慕嚷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闯冷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容