JAVA-Socket通訊 打造屬於本身的聊天室(服務端)

咱們天天都在使用着微信、QQ等聊天軟件,但不知你是否有想過這些聊天軟件是如何實現的?是否想過要製做一個屬於本身的聊天室?java

本篇博客將帶你打造一個簡單的屬於本身的聊天室,將cmd做爲聊天窗口,可經過內網,與周圍的小夥伴相互通訊,固然也能夠掛到服務器上,實現經過外網的通訊。同時還能經過服務端窗口對連入的用戶進行管理。數據庫

先來看看我作的效果服務器

這是服務器控制界面微信

輸入端口號,點擊啓動,再打開cmd,輸入telnet localhost 端口號,而後輸入帳號密碼登錄ide

輸入消息this

 

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

首先咱們須要先創建好用戶的信息線程

UserInfo.java3d

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;
    }
}

一個聊天室,咱們能夠將其分爲服務端和客戶端,而通訊的簡易過程以下圖所示code

 

對於客戶端,咱們須要作的是一、驗證用戶登錄信息。二、接收用戶發送的信息並轉發給目標用戶

服務端目前則使用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
        
    }

}

這樣咱們的聊天室就大功告成了。(這個聊天室的客戶端界面我還沒作,打算對上面代碼從新整理以後再寫。固然也期待你爲這個聊天室添加上一個界面)

相關文章
相關標籤/搜索