線上聊天項目結構圖: 多用戶登陸效果圖: 多用戶聊天效果圖: 資料庫效果圖: 重新構建了Server類,使用了Gson方法,通過解析Json字元串,增加Info類,簡化判斷過程。 Server類代碼如下: 新增的工具類Info,通過info對象的get方法把獲得服務端得到的各種字元串直接歸類了。 I ...
線上聊天項目結構圖:
多用戶登陸效果圖:
多用戶聊天效果圖:
資料庫效果圖:
重新構建了Server類,使用了Gson方法,通過解析Json字元串,增加Info類,簡化判斷過程。
Server類代碼如下:
package com.swift.server; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import java.util.Vector; import com.google.gson.Gson; import com.swift.jdbc.DBAdd; import com.swift.jdbc.DBLogin; import com.swift.other.User; import com.swift.util.Info; public class Server { boolean started = false; ServerSocket ss = null; Socket s = null; List<Client> clients = new ArrayList<Client>(); Vector<String> list = new Vector<String>(); public static void main(String[] args) { new Server().fun(); } private void fun() { try { ss = new ServerSocket(8888); started = true; } catch (BindException e) { System.out.println("埠使用中......"); } catch (IOException e1) { e1.printStackTrace(); } try { while (started) { s = ss.accept(); System.out.println("a client connected success"); Client c = new Client(s); clients.add(c); new Thread(c).start(); } } catch (EOFException e) { System.out.println("client has closed."); } catch (Exception e) { e.printStackTrace(); } finally { try { ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Client implements Runnable { public Socket s; public DataInputStream dis; public DataOutputStream dos; private boolean connected = false; public Client(Socket s) { this.s = s; try { this.dis = new DataInputStream(s.getInputStream()); this.dos = new DataOutputStream(s.getOutputStream()); connected = true; } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { try { while (connected) { // 接收客戶端請求 String json = dis.readUTF(); Gson gson = new Gson(); Info info = gson.fromJson(json, Info.class); if (info.getState().equals("login")) { String phone = info.getPhone(); String password = info.getPassword(); System.out.println(phone); System.out.println(password); User user = new User(phone, password); boolean login = DBLogin.login(user); if (login) { dos.writeUTF("success"); dos.flush(); String userName = dis.readUTF(); // 添加進好友列表Vector list.addElement(userName); System.out.println(); // 每當有成功登陸就向各個客戶端發送完整列表 for (int i = 0; i < clients.size(); i++) { Client c = clients.get(i); System.out.println(c.s.getPort()); for (int j = 0; j < list.size(); j++) { String name = list.get(j); System.out.println(name); String json_name = "{\"state\":\"friends\",\"msg\":\"" + name + "\"}"; c.send(json_name); } } } else { dos.writeUTF("fail"); dos.flush(); } } else if (info.getState().equals("reg")) { String phone = info.getPhone(); String password = info.getPassword(); System.out.println(phone); System.out.println(password); User user = new User(phone, password); boolean flag = DBAdd.add(user); if (flag) { dos.writeUTF("success"); }else { dos.writeUTF("fail"); } } else if (info.getState().equals("chat")) { System.out.println(json); for (int i = 0; i < clients.size(); i++) { Client c = clients.get(i); c.send(json); } } } } catch (SocketException e) { System.out.println("一個登陸窗已經關閉...."); } catch (EOFException e) { System.out.println("a client has been closed,can't read message from it."); } catch (IOException e) { e.printStackTrace(); } finally { if (s != null) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } if (dis != null) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); } } if (dos != null) { try { dos.close(); } catch (IOException e) { e.printStackTrace(); } } } } private void send(String str) { try { this.dos.writeUTF(str); this.dos.flush(); } catch (SocketException e) { clients.remove(this); } catch (IOException e) { e.printStackTrace(); } } } }
新增的工具類Info,通過info對象的get方法把獲得服務端得到的各種字元串直接歸類了。
Info類代碼如下:
package com.swift.util; public class Info { private String state; private String msg; private String phone; private String password; public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
重構了登陸視窗的登陸和註冊請求
登陸視窗的代碼如下:
package com.swift.frame; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ConnectException; import java.net.Socket; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.border.TitledBorder; import com.swift.util.Center; public class LoginDialog extends JDialog { private static final long serialVersionUID = 1L; private JPasswordField passwordField_2; private JPasswordField passwordField_1; private JTextField textField_2; private JTextField textField; Socket s; DataOutputStream dos; DataInputStream dis; public static void main(String args[]) { JFrame.setDefaultLookAndFeelDecorated(true); JDialog.setDefaultLookAndFeelDecorated(true); try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (InstantiationException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (UnsupportedLookAndFeelException e1) { e1.printStackTrace(); } EventQueue.invokeLater(new Runnable() { public void run() { try { LoginDialog dialog = new LoginDialog(); dialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); dialog.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } public LoginDialog() { super(); setResizable(false); setTitle("線上聊天登錄框"); getContentPane().setLayout(null); setBounds(100, 100, 427, 301);// 註冊時高度為578,不註冊是301 // 設置視窗居中 this.setLocation(Center.getPoint(this.getSize())); final JTextField textField_1 = new JTextField(); textField_1.setBounds(148, 90, 192, 42); getContentPane().add(textField_1); final JLabel label = new JLabel(); label.setText("帳 號"); label.setBounds(76, 102, 66, 18); getContentPane().add(label); final JLabel label_1 = new JLabel(); label_1.setText("密 碼"); label_1.setBounds(76, 167, 66, 18); getContentPane().add(label_1); final JPasswordField passwordField = new JPasswordField(); passwordField.setBounds(148, 155, 192, 42); getContentPane().add(passwordField); final JButton button_1 = new JButton(); button_1.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { String phone = new String(textField_1.getText()).trim(); String password = new String(passwordField.getPassword()).trim(); if (!phone.equals("") && !password.equals("")) { try { String json_login = "{\"state\":\"login\",\"phone\":\"" + phone + "\",\"password\":\"" + password + "\"}"; dos.writeUTF(json_login); dos.flush(); String flag = dis.readUTF(); if (flag.equals("success")) { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "登錄成功"); // 把登陸成功的用戶發送給服務端,作為好友列表信息 dos.writeUTF(phone); dos.flush(); // 登陸窗消失 LoginDialog.this.dispose(); // 好友列表窗出現 new FriendsFrame(phone, s); } else if (flag.equals("fail")) { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "登錄失敗"); } } catch (IOException e1) { e1.printStackTrace(); } } else { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "用戶名密碼必須填寫..."); return; } } }); button_1.setText("登錄"); button_1.setBounds(230, 222, 106, 36); getContentPane().add(button_1); final JButton button = new JButton(); button.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (LoginDialog.this.getHeight() == 301) { LoginDialog.this.setSize(427, 578); } else { LoginDialog.this.setSize(427, 301); } // 設置視窗不斷居中 LoginDialog.this.setLocation(Center.getPoint(LoginDialog.this.getSize())); } }); button.setText("註冊"); button.setBounds(76, 222, 106, 36); getContentPane().add(button); final JPanel panel = new JPanel(); panel.setLayout(null); panel.setBorder(new TitledBorder(null, "註冊用戶", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); panel.setBounds(10, 278, 401, 226); getContentPane().add(panel); final JLabel label_2 = new JLabel(); label_2.setBounds(44, 41, 65, 18); label_2.setText("手機號:"); panel.add(label_2); textField = new JTextField(); textField.setBounds(115, 35, 225, 30); panel.add(textField); final JButton button_2 = new JButton(); button_2.setText("發送驗證"); button_2.setBounds(243, 75, 97, 30); panel.add(button_2); textField_2 = new JTextField(); textField_2.setBounds(115, 104, 95, 30); panel.add(textField_2); final JLabel label_3 = new JLabel(); label_3.setText("驗證碼:"); label_3.setBounds(44, 110, 65, 18); panel.add(label_3); passwordField_1 = new JPasswordField(); passwordField_1.setBounds(115, 143, 231, 30); panel.add(passwordField_1); passwordField_2 = new JPasswordField(); passwordField_2.setBounds(115, 175, 231, 30); panel.add(passwordField_2); final JLabel label_4 = new JLabel(); label_4.setText("密 碼:"); label_4.setBounds(44, 149, 65, 18); panel.add(label_4); final JLabel label_5 = new JLabel(); label_5.setText("驗證密碼:"); label_5.setBounds(44, 181, 65, 18); panel.add(label_5); final JButton button_3 = new JButton(); button_3.setBounds(47, 510, 97, 30); getContentPane().add(button_3); button_3.setText("放棄"); final JButton button_4 = new JButton(); button_4.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { String phone = textField.getText(); String password = null; String str1 = new String(passwordField_1.getPassword()).trim(); String str2 = new String(passwordField_2.getPassword()).trim(); if (!phone.equals("") && !str1.equals("") && !str2.equals("")) { if (str1.equals(str2)) { password = new String(passwordField_2.getPassword()).trim(); try { String json_reg = "{\"state\":\"reg\",\"phone\":\"" + phone + "\",\"password\":\"" + password + "\"}"; dos.writeUTF(json_reg); dos.flush(); String flag = dis.readUTF(); if (flag.equals("success")) { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "註冊成功..."); textField.setText(""); passwordField_1.setText(""); passwordField_2.setText(""); } else { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "註冊失敗..."); } } catch (IOException e1) { e1.printStackTrace(); } } else { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "輸入密碼不一致..."); System.out.println("輸入密碼不一致..."); passwordField_1.setText(""); passwordField_2.setText(""); } } else { javax.swing.JOptionPane.showMessageDialog(LoginDialog.this, "用戶名密碼必須填寫..."); return; } } }); button_4.setBounds(262, 510, 97, 30); getContentPane().add(button_4); button_4.setText("註冊用戶"); connect(); } public void connect() { try { s = new Socket("127.0.0.1", 8888);// 以本機做server System.out.println("一個客戶端登陸中....!"); dos = new DataOutputStream(s.getOutputStream()); dis = new DataInputStream(s.getInputStream()); } catch (ConnectException e) { System.out.println("服務端異常........."); System.out.println("請確認服務端是否開啟........."); } catch (IOException e) { e.printStackTrace(); } } }
對好友列表窗的死迴圈傻傻等待服務端信息,進行了重構,通過Gson方法自動判斷所獲得的Json串信息到底是好友的更新還是聊天信息的更新。
好友列表窗代碼如下:
package com.swift.frame; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTabbedPane; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.swift.util.Info; public class FriendsFrame extends JFrame { private static final long serialVersionUID = 1L; private Socket s; private DataOutputStream dos; private DataInputStream dis; private boolean connected = false; Vector<String> names = new Vector<String>(); JList<String> list = null; public FriendsFrame(String name, Socket socket) { super("歡迎 " + ":" + socket.getLocalPort()); this.s = socket; connected = true; names.add("登錄用戶"); try { this.dos = new DataOutputStream(s.getOutputStream()); this.dis = new DataInputStream(s.getInputStream()); } catch (IOException e) { e.printStackTrace(); } JFrame.setDefaultLookAndFeelDecorated(true); JDialog.setDefaultLookAndFeelDecorated(true); try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (InstantiationException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (UnsupportedLookAndFeelException e1) { e1.printStackTrace(); } setBounds(100, 100, 247, 581); setVisible(true); final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); getContentPane().add(panel, BorderLayout.NORTH); final JLabel label = new JLabel(new ImageIcon("Images/logo.jpg")); label.setText("New JLabel"); panel.add(label, BorderLayout.WEST); label.setPreferredSize(new Dimension(74, 74)); final JPanel panel_1 = new JPanel(); panel_1.setLayout(new BorderLayout()); panel.add(panel_1, BorderLayout.CENTER); final JLabel advancingSwiftLabel = new JLabel(); advancingSwiftLabel.setText(name); panel_1.add(advancingSwiftLabel, BorderLayout.CENTER); final JLabel neverWasterLabel = new JLabel(); neverWasterLabel.setText("Never waste time any more"); panel_1.add(neverWasterLabel, BorderLayout.SOUTH); final JPanel panel_2 = new JPanel(); panel_2.setLayout(new BorderLayout()); getContentPane().add(panel_2, BorderLayout.SOUTH); final JPanel panel_3 = new JPanel(); final FlowLayout flowLayout = new FlowLayout(); flowLayout.setAlignment(FlowLayout.LEFT); panel_3.setLayout(flowLayout); panel_2.add(panel_3); final JButton button = new JButton(); panel_3.add(button); button.setHorizontalTextPosition(SwingConstants.LEFT); button.setHorizontalAlignment(SwingConstants.LEFT); button.setText("設置"); final JButton button_1 = new JButton(); panel_3.add(button_1); button_1.setText("查找"); final JPanel panel_4 = new JPanel(); panel_2.add(panel_4, BorderLayout.EAST); final JButton button_2 = new JButton(); panel_4.add(button_2); button_2.setText("退出"); final JTabbedPane tabbedPane = new JTabbedPane(); getContentPane().add(tabbedPane, BorderLayout.CENTER); final JPanel panel_5 = new JPanel(); tabbedPane.addTab("好友列表", null, panel_5, null); list = new JList<String>(); panel_5.add(list); final JPanel panel_6 = new JPanel(); tabbedPane.addTab("群聊", null, panel_6, null); final JPanel panel_7 = new JPanel(); tabbedPane.addTab("聊天記錄", null, panel_7, null); this.setDefaultCloseOperation(EXIT_ON_CLOSE); final JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); final JMenu menu = new JMenu(); menu.setText("操作"); menuBar.add(menu); final JMenuItem newItemMenuItem = new JMenuItem(); newItemMenuItem.setText("設置"); menu.add(newItemMenuItem); final JMenuItem newItemMenuItem_1 = new JMenuItem(); newItemMenuItem_1.setText("空間"); menu.add(newItemMenuItem_1); final JMenuItem newItemMenuItem_2 = new JMenuItem(); newItemMenuItem_2.setText("郵箱"); menu.add(newItemMenuItem_2); final JMenu menu_1 = new JMenu(); menu_1.setText("會員"); menu.add(menu_1); final JMenuItem newItemMenuItem_3 = new JMenuItem(); newItemMenuItem_3.setText("會員官網"); menu_1.add(newItemMenuItem_3); final JMenuItem newItemMenuItem_4 = new JMenuItem(); newItemMenuItem_4.setText("會員專區"); menu_1.add(newItemMenuItem_4); menu.addSeparator(); final JMenu menu_2 = new JMenu(); menu_2.setText("安全"); menu.add(menu_2); final JMenuItem newItemMenuItem_5 = new JMenuItem(); newItemMenuItem_5.setText("緊急凍結"); menu_2.add(newItemMenuItem_5); final JMenuItem newItemMenuItem_6 = new JMenuItem(); newItemMenuItem_6.setText("密碼保護"); menu_2.add(newItemMenuItem_6); final JMenuItem newItemMenuItem_7 = new JMenuItem(); newItemMenuItem_7.setText("退出"); menu.add(newItemMenuItem_7); final FlowLayout flowLayout_1 = new FlowLayout(); flowLayout_1.setAlignment(FlowLayout.RIGHT); this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { disconnect(); System.exit(0); } }); // 調用傻傻的等待接收列表信息 new Thread(new WaitingReceive()).start(); // 雙擊激活聊天對話框 list.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { new ChatFrame(s); } } }); } public void disconnect() { try { if (dos != null) dos.close(); if (dis != null) dis.close(); if (s != null) s.close(); } catch (IOException e) { e.printStackTrace(); } } class WaitingReceive implements Runnable { @Override public void run() { try { while (connected) { String json = dis.readUTF(); Gson gson = new Gson(); Info info = gson.fromJson(json, Info.class); System.out.println(json + "~~~~~~~~~~~~~~~~~~~~~~~~"); if (info.getState().equals("friends")) { int flag = 0; for (int i = 0; i < names.size(); i++) { if (info.getMsg().equals(names.get(i))) { flag = 1; } } if (flag == 0) { names.add(info.getMsg()); } list.setListData(names); } if(info.getState().equals("chat")) { ChatFrame.showTa(info.getMsg()); System.out.println(info.getMsg()); } } } catch (SocketException e) { System.out.println("a client has been closed!"); } catch (IOException e) { e.printStackTrace(); } } } /** * WindowBuilder generated method.<br> * Please don't remove this method or its invocations.<br> * It used by WindowBuilder to associate the {@link javax.swing.JPopupMenu} with * parent. */ private static void addPopup(Component component, final JPopupMenu popup) { component.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) showMenu(e); } public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) showMenu(e); } private void showMenu(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } }); } }
對聊天窗異常進行了處理,發現聊天窗與好友列表窗同時傻傻等待服務端信息,會引發衝突,聊天窗後於好友窗打開所以接不到任何信息。
所以將聊天窗的死迴圈去掉,而增加一個static靜態方法showTa()用來在聊天窗之外供好友窗通過類名.(點)的方式調用,以改變ta(textArea)中的內容。
聊天窗代碼如下:
package com.swift.frame; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ConnectException; import java.net.Socket; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPan