1. 程式人生 > >JAVA-Socket通訊 打造屬於自己的聊天室(初級版)

JAVA-Socket通訊 打造屬於自己的聊天室(初級版)

我們每天都在使用著微信、QQ等聊天軟體,但不知你是否有想過這些聊天軟體是如何實現的?是否想過要製作一個屬於自己的聊天室?

本篇部落格將帶你打造一個簡單的屬於自己的聊天室,將cmd作為聊天視窗,可通過內網,與周圍的小夥伴相互通訊,當然也可以掛到伺服器上,實現通過外網的通訊。同時還能通過服務端視窗對連入的使用者進行管理。

先來看看我做的效果

這是伺服器控制介面

輸入埠號,點選啟動,再開啟cmd,輸入telnet localhost 埠號,然後輸入賬號密碼登陸

輸入訊息

下面就來講講如何實現的吧

首先我們需要先建立好使用者的資訊

UserInfo.java

public
class UserInfo { private String name; private String possward; public String getName(){ return this.name; } public void setName(String name) { this.name=name; } public void setPwd(String possward) { this.possward=possward; } }

一個聊天室,我們可以將其分為服務端和客戶端,而通訊的簡易過程如下圖所示

對於客戶端,我們需要做的是1、驗證使用者登陸資訊。2、接收使用者傳送的資訊並轉發給目標使用者

服務端目前則使用cmd進行

首先,我們先建立一個簡易的儲存賬號密碼的資料庫

DaoTools.java

public class DaoTools {
    //記憶體使用者資訊資料庫
    private static Map<String,UserInfo>userDB=new HashMap();
    
    
    public static boolean checkLogin(UserInfo user) {
        
//驗證使用者名稱是否存在 if(userDB.containsKey((user.getName()))){ return true; } System.out.println("認證失敗!:"+user.getName()); return false; } //系統內部自動建立10個賬號 static { for(int i=0;i<10;i++) { UserInfo user=new UserInfo(); user.setName("user"+i); user.setPwd("pwd"+i); userDB.put(user.getName(), user); } } }

服務端伺服器建立

ChatServer.java

public class ChatServer extends Thread {
    private int port;//
    private boolean running = false;// 伺服器是否執行中的標記
    private ServerSocket sc;// 伺服器物件

    /*
     * 建立伺服器物件時,必須傳入埠號
     * 
     * @param port:伺服器所在埠號
     */
    public ChatServer(int port) {
        this.port = port;
    }

    // 執行緒中啟動伺服器
    public void run() {
        setupServer();
    }

    // 在指定埠上啟動伺服器
    public void setupServer() {
        try {
            ServerSocket sc = new ServerSocket(this.port);
            running = true;
            System.out.println("伺服器建立成功:" + port);
            while (running) {
                Socket client = sc.accept();// 等待連結進入
                System.out.println("進入了一個客戶機連線" + client.getRemoteSocketAddress());
                // 啟動一個處理執行緒,去處理這個連結物件
                ServerThread ct = new ServerThread(client);
                ct.start();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /*
     * 查詢伺服器是否在執行
     * 
     * @return: true為執行中
     */
    public boolean isRunning() {
        return this.running;
    }

    // 關閉伺服器
    public void stopchatServer() {
        this.running = false;
        try {
            sc.close();
        } catch (Exception e) {}
    }
}

驗證使用者登陸資訊,建立伺服器執行緒

 ServerThread.java

public class ServerThread extends Thread {
    private Socket client;//執行緒中處理的客戶物件
    private OutputStream ous;//輸出流物件
    private UserInfo user;//這個執行緒處理物件代表的使用者的資訊
    //建立物件時必須傳入一個Socket物件
    public ServerThread(Socket client) {
        this.client=client;
    }
    //獲取這個執行緒物件代表的使用者物件
    public UserInfo getOwerUser() {
        return this.user;
    }
    public void run() {
        processSocket();
    }
    
    public void sendMsg2Me(String msg) {    
        try {
            msg+="\r\n";
            ous.write(msg.getBytes());
            ous.flush();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    //讀取客戶機發來的訊息
    private void processSocket() {
        try {
            InputStream ins=client.getInputStream();
            ous=client.getOutputStream();
            //將輸入流ins封裝為可以讀取一行字串也就是以\r\n結尾的字串
            BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
            sendMsg2Me("歡迎您來聊天!請輸入你的使用者名稱:");
            String userName=brd.readLine();
            sendMsg2Me(userName+"請輸入你的密碼");
            String pws=brd.readLine();
            user=new UserInfo();
            user.setName(userName);
            user.setPwd(pws);
            //呼叫資料庫模組,驗證使用者是否存在
            boolean loginState=DaoTools.checkLogin(user);
            if(!loginState) {//不存在則賬號關閉
                this.closeMe();
                return;
            }
            ChatTools.addClient(this);//認證成功:將這個物件加入伺服器佇列
            String input=brd.readLine();//一行一行的讀取客戶機發來的訊息
            while(!"bye".equals(input)) {
                System.out.println("伺服器收到的是"+input);
                //讀到一條訊息後,就傳送給其他的客戶機
                ChatTools.castMsg(this.user, input);
                input=brd.readLine();//讀取下一條
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        ChatTools.castMsg(this.user, "我下線了,再見!");
        this.closeMe();
    }
    //關閉這個執行緒
    public void closeMe() {
        try {
            client.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

通過服務端對使用者操作

 ChatTools.java

public class ChatTools {
    // 儲存處理執行緒的佇列物件
    private static List<ServerThread> stList = new ArrayList();

    private ChatTools() {
    }

    /*
     * 取得儲存處理執行緒物件的佇列
     */
    public static List<ServerThread> getAllThread() {
        return stList;
    }

    /*
     * 將一個客戶對應的處理執行緒物件加入到佇列中
     * 
     * @param ct:處理執行緒物件
     */
    public static void addClient(ServerThread ct) {
        // 通知大家一下,有人上限了
        castMsg(ct.getOwerUser(), "我上線啦!!目前人數" + stList.size());
        stList.add(ct);
        SwingUtilities.updateComponentTreeUI(MainServerUI.table_onlineUser);
    }

    // 給佇列中某一個使用者發訊息
    public static void sendMsg2One(int index, String msg) {
        stList.get(index).sendMsg2Me(msg);
    }

    // 根據表中選中索引,取得處理執行緒物件對應的使用者物件
    public static UserInfo getUser(int index) {
        return stList.get(index).getOwerUser();
    }

    /*
     * 移除佇列中指定位置的處理執行緒物件,介面踢人時呼叫
     * 
     * @param index:要移除的位置
     */
    public static void removeClient(int index) {
        stList.remove(index).closeMe();
    }

    /*
     * 從佇列中移除一個使用者物件對應的處理執行緒
     * 
     * @param user:要一處的使用者物件
     */
    public static void removeAllClient(UserInfo user) {
        for (int i = 0; i < stList.size(); i++) {
            ServerThread ct = stList.get(i);
            stList.remove(i);
            ct.closeMe();
            ct = null;
            castMsg(user, "我下線啦");
        }
    }

    /*
     * 伺服器關閉時,呼叫這個方法,移除,停止佇列中所有處理執行緒物件
     */
    public static void removeAllClient() {
        for (int i = 0; i < stList.size(); i++) {
            ServerThread st = stList.get(i);
            st.sendMsg2Me("系統訊息:伺服器即將關閉");
            st.closeMe();
            stList.remove(i);
            System.out.println("關閉了一個..." + i);
            st = null;
        }
    }

    /*
     * 將一條訊息傳送給佇列中的其他客戶機處理物件
     * 
     * @param sender:傳送者使用者物件
     * 
     * @param msg:要傳送的訊息內容
     */
    public static void castMsg(UserInfo sender, String msg) {
        msg = sender.getName() + "說:" + msg;
        for (int i = 0; i < stList.size(); i++) {
            ServerThread st = stList.get(i);

            // ServerThread類中定義有一個將訊息傳送出去的方法
                st.sendMsg2Me(msg);// 傳送給每一個客戶機
        }
    }
}

服務端介面

MainServerUI.java

package MyChatV1;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/*
 * 伺服器端管理介面程式
 * 1.啟/停 
 * 2.釋出公告訊息
 * 3.顯示線上使用者資訊 
 * 4.踢人 
 * 5.對某一個人發訊息 
 * 
 */
public class MainServerUI {

    private ChatServer cserver;// 伺服器物件
    private JFrame jf;// 管理介面
    static JTable table_onlineUser;// 線上使用者表
    private JTextField jta_msg;// 傳送訊息輸入框
    private JTextField jta_port;// 伺服器埠號輸入端
    private JButton bu_control_chat;// 啟動伺服器的按鈕

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MainServerUI mu = new MainServerUI();
        mu.showUI();
    }

    // 初始化介面
    public void showUI() {
        jf = new JFrame("javaKe伺服器管理程式");
        jf.setSize(500, 300);
        FlowLayout f1 = new FlowLayout();
        jf.setLayout(f1);

        JLabel la_port = new JLabel("伺服器埠:");
        jf.add(la_port);
        jta_port = new JTextField(4);
        jta_port.addKeyListener(new KeyAdapter() {
            public void keyTyped(KeyEvent e) {
                int keyChar = e.getKeyChar();
                if (keyChar >= KeyEvent.VK_0 && keyChar <= KeyEvent.VK_9) {

                } else {
                    e.consume();// 遮蔽掉非法輸入
                }
            }
        });
        jf.add(jta_port);
        bu_control_chat = new JButton("啟動伺服器");
        jf.add(bu_control_chat);
        //啟動的事件監聽器
        bu_control_chat.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                actionServer();
            }
        });

        JLabel la_msg = new JLabel("要傳送的訊息");
        jf.add(la_msg);
        // 伺服器要傳送訊息的輸入框
        jta_msg = new JTextField(30);
        // 定義一個監聽器物件:傳送廣播訊息
        ActionListener sendCaseMsgAction = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                sendAllMsg();
            }
        };

        // 給輸入框加航事件監聽器,按回車時傳送
        jta_msg.addActionListener(sendCaseMsgAction);
        JButton bu_send = new JButton("Send");
        // 給按鈕加上傳送廣播訊息的監聽器
        bu_send.addActionListener(sendCaseMsgAction);
        jf.add(jta_msg);
        jf.add(bu_send);

        // 介面上用以顯示線上使用者列表的表格
        table_onlineUser = new JTable();
        // 建立我們自己的Model物件:建立時,傳入處理執行緒列表
        List<ServerThread> sts = ChatTools.getAllThread();
        UserInfoTableModel utm = new UserInfoTableModel(sts);
        table_onlineUser.setModel(utm);
        // 將表格放到滾動面板物件上
        JScrollPane scrollPane = new JScrollPane(table_onlineUser);
        // 設定表格在面板上的大小
        table_onlineUser.setPreferredScrollableViewportSize(new Dimension(400, 100));
        // 超出大小後,JScrollPane自動出現滾動條
        scrollPane.setAutoscrolls(true);
        jf.add(scrollPane);// 將scrollPane物件加到介面上

        // 取得表格上的彈出選單物件,加到表格上
        JPopupMenu pop = getTablePop();
        table_onlineUser.setComponentPopupMenu(pop);
        jf.setVisible(true);
        jf.setDefaultCloseOperation(3);// 介面關閉時,程式退出

    }

    /*
     * 建立表格上的彈出選單物件,實現發信,踢人功能
     */
    private JPopupMenu getTablePop() {
        JPopupMenu pop = new JPopupMenu();// 彈出選單物件
        JMenuItem mi_send = new JMenuItem("發信");
        ;// 選單項物件
        mi_send.setActionCommand("send");// 設定選單命令關鍵字
        JMenuItem mi_del = new JMenuItem("踢掉");// 選單項物件
        mi_del.setActionCommand("del");// 設定選單命令關鍵字
        // 彈出選單上的事件監聽器物件
        ActionListener al = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String s = e.getActionCommand();
                // 哪個選單項點選了,這個s就是其設定的ActionCommand
                popMenuAction(s);
            }
        };
        mi_send.addActionListener(al);
        mi_del.addActionListener(al);// 給選單加上監聽器
        pop.add(mi_send);
        pop.add(mi_del);
        return pop;
    }

    // 處理彈出選單上的事件
    private void popMenuAction(String command) {
        // 得到在表格上選中的行
        final int selectIndex = table_onlineUser.getSelectedRow();
        if (selectIndex == -1) {
            JOptionPane.showMessageDialog(jf, "請選中一個使用者");
            return;
        }
        if (command.equals("del")) {
            // 從執行緒中移除處理執行緒物件
            ChatTools.removeClient(selectIndex);
        } else if (command.equals("send")) {
            UserInfo user = ChatTools.getUser(selectIndex);
            final JDialog jd = new JDialog(jf, true);// 傳送對話方塊
            jd.setLayout(new FlowLayout());
            jd.setTitle("您將對" + user.getName() + "發信息");
            jd.setSize(200, 100);
            final JTextField jtd_m = new JTextField(20);
            //jtd_m.setPreferredSize(new Dimension(150,30));
            JButton jb = new JButton("傳送!");
        //    jb.setPreferredSize(new Dimension(50,30));
            jd.add(jtd_m);
            jd.add(jb);
            // 傳送按鈕的事件實現
            jb.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    System.out.println("傳送了一條訊息啊...");
                    String msg = "系統悄悄說:" + jtd_m.getText();
                    ChatTools.sendMsg2One(selectIndex, msg);
                    jtd_m.setText("");// 清空輸入框
                    jd.dispose();
                }
            });
            jd.setVisible(true);
        } else {
            JOptionPane.showMessageDialog(jf, "未知選單:" + command);
        }
        // 重新整理表格
        SwingUtilities.updateComponentTreeUI(table_onlineUser);
    }

    // 按下發送伺服器訊息的按鈕,給所有線上使用者傳送訊息
    private void sendAllMsg() {
        String msg = jta_msg.getText();// 得到輸入框的訊息
        UserInfo user = new UserInfo();
        user.setName("系統");
        user.setPwd("pwd");
        ChatTools.castMsg(user, msg); // 傳送
        jta_msg.setText("");// 清空輸入框

    }

    // 響應啟動/停止按鈕事件
    private void actionServer() {
        // 1.要得到伺服器狀態
        if (null == cserver) {
            String sPort = jta_port.getText();// 得到輸入的埠
            int port = Integer.parseInt(sPort);
            cserver = new ChatServer(port);
            cserver.start();
            jf.setTitle("QQ伺服器管理程式 :正在執行中");
            bu_control_chat.setText("Stop!");
        } else if (cserver.isRunning()) {// 己經在執行
            cserver.stopchatServer();
            cserver = null;
            // 清除所有己在執行的客戶端處理執行緒
            ChatTools.removeAllClient();
            jf.setTitle("QQ伺服器管理程式 :己停止");
            bu_control_chat.setText("Start!");
        }

    }
}

其中用到了一個自己建立的類UserInfoTableModel,用於選單中的資訊顯示

public class UserInfoTableModel implements TableModel {
    List<ServerThread> sts = ChatTools.getAllThread();
    public UserInfoTableModel(List<ServerThread> sts) {
        this.sts=sts;
        
    }
    @Override
    public void addTableModelListener(TableModelListener l) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        // TODO Auto-generated method stub
        return String.class;
    }

    @Override
    public int getColumnCount() {
        // TODO Auto-generated method stub
        return 1;
    }

    @Override
    public String getColumnName(int columnIndex) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getRowCount() {
        // TODO Auto-generated method stub
        return sts.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
            return sts.get(rowIndex).getOwerUser().getName();
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void removeTableModelListener(TableModelListener l) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
        
    }

}

這樣我們的聊天室就大功告成了。(這個聊天室的客戶端介面我還沒做,打算對上面程式碼重新整理之後再寫。當然也期待你為這個聊天室新增上一個介面)