JAVA-Socket通訊 打造屬於自己的聊天室(初級版)
阿新 • • 發佈:2018-12-12
我們每天都在使用著微信、QQ等聊天軟體,但不知你是否有想過這些聊天軟體是如何實現的?是否想過要製作一個屬於自己的聊天室?
本篇部落格將帶你打造一個簡單的屬於自己的聊天室,將cmd作為聊天視窗,可通過內網,與周圍的小夥伴相互通訊,當然也可以掛到伺服器上,實現通過外網的通訊。同時還能通過服務端視窗對連入的使用者進行管理。
先來看看我做的效果
這是伺服器控制介面
輸入埠號,點選啟動,再開啟cmd,輸入telnet localhost 埠號,然後輸入賬號密碼登陸
輸入訊息
下面就來講講如何實現的吧
首先我們需要先建立好使用者的資訊
UserInfo.java
publicclass 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 } }
這樣我們的聊天室就大功告成了。(這個聊天室的客戶端介面我還沒做,打算對上面程式碼重新整理之後再寫。當然也期待你為這個聊天室新增上一個介面)