用JavaSocket编程开发聊天室,附超详细注释
用JavaSocket編程開發聊天室
大二下冊的JavaWeb課程設計,使用的是eclipse。
一、實現功能
登錄:用Java圖形用戶界面編寫聊天室服務器端和客戶端,支持多個客戶端連接到一個服務器。每個客戶端能夠輸入賬號。
群聊:可以實現群聊(聊天記錄顯示在所有客戶端界面)。
好友列表:完成好友列表在各個客戶端上顯示。
私聊:可以實現私人聊天,用戶可以選擇某個其他用戶,單獨發送信息,接受私聊消息方可以直接彈出消息框。
踢人:服務器能夠群發系統消息,能夠強行讓某些用戶下線。
更新:客戶端的上線下線要求能夠在其他客戶端上面實時刷新。
二、思路概述
-
分為服務器端和客戶端。
-
服務器端相當于一個轉發器的功能,所有客戶端的消息都先發給服務器端,由服務器端再轉發給對應的客戶端。
-
不同類型的消息格式不同,服務器端根據消息的格式來判斷事件類型,再執行相應的功能。
服務器端
? ? ? 因為運行的過程中隨時會有客戶端連上服務器,所以服務器端需要一個線程來等待客戶端的鏈接。其次,每一個服務器端的用戶隨時都有可能和服務器就發送消息,因此每新增一個用戶就需要為該用戶建立一個聊天的線程。
服務器端還需要具備踢人、群發消息、發送消息的功能。這些功能的本質其實就是發送對應格式消息(消息格式見下文),只是發送的消息格式不同罷了。
客戶端
? ? ? 客戶端需要實現的主要功能是群發消息和私發消息,并且通過收到的消息格式判斷服務器發送過來的消息,再進行響應的代碼。
三、消息格式
1.上線 : login#nickName
2.下線 : offline#nickName
3.系統發消息 : All#content
4.用戶群發消息 : msg#nickName#content
5.用戶私發消息 : smsg#sender#receiver#content
6.用戶第一次私發消息 : fsmsg#sender#receiver#content
7.服務器端為新加入的客戶端建立好友列表 : users#nickName
消息通過split("#")函數將字符串轉換成數組,根據strs[0],即第一個值可以判斷消息的類型,后面的值判斷消息的客戶端/發送者/接收者。
四、運行結果
客戶端/服務器端界面:
私聊界面:
五、源代碼
server.java:
package chat;import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList;class server extends JFrame implements Runnable, ListSelectionListener, ActionListener {private Socket s = null;private ServerSocket ss = null;private ArrayList<ChatThread> users = new ArrayList<ChatThread>(); //容量能夠動態增長的數組DefaultListModel<String> dl = new DefaultListModel<String>();private JList<String> userList = new JList<String>(dl);//顯示對象列表并且允許用戶選擇一個或多個項的組件。單獨的模型 ListModel 維護列表的內容。private JPanel jpl = new JPanel();private JButton jbt = new JButton("踢出聊天室");private JButton jbt1 = new JButton("群發消息");//群發消息輸入欄private JTextField jtf = new JTextField();public server() throws Exception{this.setTitle("服務器端");this.add(userList, "North");//放在北面this.add(jpl, "South");//僅將群發消息輸入欄設為一欄jtf.setColumns(1);jpl.setLayout(new BorderLayout());jpl.add(jtf, BorderLayout.NORTH);jpl.add(jbt,BorderLayout.EAST);//踢出聊天室jpl.add(jbt1, BorderLayout.WEST);//群發消息//實現群發jbt1.addActionListener(this);//實現踢人jbt.addActionListener(this);this.setDefaultCloseOperation(EXIT_ON_CLOSE);this.setLocation(400,100);this.setSize(500, 400);this.setVisible(true);this.setAlwaysOnTop(true);ss = new ServerSocket(9999);new Thread(this).start();//監聽用戶端的加入}@Overridepublic void run() {while(true){try{s = ss.accept();ChatThread ct = new ChatThread(s); //為該客戶開一個線程users.add(ct); //將每個線程加入到users//發送Jlist里的用戶登陸信息,為了防止后面登陸的用戶無法更新有前面用戶的好友列表ListModel<String> model = userList.getModel();//獲取Jlist的數據內容for(int i = 0; i < model.getSize(); i++){ct.ps.println("USERS#" + model.getElementAt(i));}ct.start();}catch (Exception ex){ex.printStackTrace();javax.swing.JOptionPane.showMessageDialog(this,"服務器異常!");System.exit(0);}}}//群發消息按鈕點擊事件監聽@Overridepublic void actionPerformed(ActionEvent e) {String label = e.getActionCommand();if(label.equals("群發消息")){handleAll();}else if(label.equals("踢出聊天室")){try {handleExpel();} catch (IOException e1) {e1.printStackTrace();}}}public void handleAll(){if(!jtf.getText().equals("")){sendMessage("ALL#" + jtf.getText());//發送完后,是輸入框中內容為空jtf.setText("");}}//群發消息public void handleExpel() throws IOException {sendMessage("OFFLINE#" + userList.getSelectedValuesList().get(0));dl.removeElement(userList.getSelectedValuesList().get(0));//更新defaultModeluserList.repaint();//更新Jlist}//踢人public class ChatThread extends Thread{Socket s = null;private BufferedReader br = null;private PrintStream ps = null;public boolean canRun = true;String nickName = null;public ChatThread(Socket s) throws Exception{this.s = s;br = new BufferedReader(new InputStreamReader(s.getInputStream()));ps = new PrintStream(s.getOutputStream());}public void run(){while(canRun){try{String msg = br.readLine();//接收客戶端發來的消息String[] strs = msg.split("#");if(strs[0].equals("LOGIN")){//收到來自客戶端的上線消息nickName = strs[1];dl.addElement(nickName);userList.repaint();sendMessage(msg);}else if(strs[0].equals("MSG") || strs[0].equals("SMSG") || strs[0].equals("FSMSG")){sendMessage(msg);}else if(strs[0].equals("OFFLINE")){//收到來自客戶端的下線消息sendMessage(msg);//System.out.println(msg);dl.removeElement(strs[1]);// 更新List列表userList.repaint();}}catch (Exception ex){}}}}public void sendMessage(String msg){ //服務器端發送給所有用戶for(ChatThread ct : users){ct.ps.println(msg);}}@Overridepublic void valueChanged(ListSelectionEvent e) {// TODO 自動生成的方法存根}public static void main(String[] args) throws Exception{new server();} }client.java:
package chat;import javax.swing.*; import com.sun.org.apache.bcel.internal.generic.NEW; import java.awt.*; import java.awt.event.*; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket;public class client extends JFrame implements Runnable,ActionListener {//northprivate JMenuBar bar = new JMenuBar();private JMenu menu = new JMenu("關于");private JMenuItem about = new JMenuItem("關于本軟件");private JMenuItem exit = new JMenuItem("退出");JPanel north = new JPanel();//westJPanel west = new JPanel();DefaultListModel<String> dl = new DefaultListModel<String>();//用來修改JListprivate JList<String> userList = new JList<String>(dl);//用來展示和選擇JScrollPane listPane = new JScrollPane(userList);//centerJPanel center = new JPanel();JTextArea jta = new JTextArea(10,20);JScrollPane js = new JScrollPane(jta);JPanel operPane = new JPanel();//發送消息的操作面板JLabel input = new JLabel("請輸入:");JTextField jtf = new JTextField(24);JButton jButton = new JButton("發消息");private JButton jbt = new JButton("發送消息");private JButton jbt1 = new JButton("私發消息");private BufferedReader br = null;private PrintStream ps = null;private String nickName = null;//私聊面板JTextArea jTextArea = new JTextArea(11,45);JScrollPane js1 = new JScrollPane(jTextArea);JTextField jTextField = new JTextField(25);String suser = new String();double MAIN_FRAME_LOC_X;//父窗口x坐標double MAIN_FRAME_LOC_Y;//父窗口y坐標boolean FirstSecret = true;//是否第一次私聊String sender=null;//私聊發送者的名字String receiver=null;//私聊接收者的名字public client() throws Exception{//north 菜單欄bar.add(menu);menu.add(about);menu.add(exit);about.addActionListener(this);exit.addActionListener(this);BorderLayout bl = new BorderLayout();north.setLayout(bl);north.add(bar,BorderLayout.NORTH);add(north,BorderLayout.NORTH);//east 好友列表Dimension dim = new Dimension(100,150);west.setPreferredSize(dim);//在使用了布局管理器后用setPreferredSize來設置窗口大小//Dimension dim2 = new Dimension(100,150);//listPane.setPreferredSize(dim2);BorderLayout bl2 = new BorderLayout();west.setLayout(bl2);west.add(listPane,BorderLayout.CENTER);//顯示好友列表add(west,BorderLayout.EAST);userList.setFont(new Font("隸書",Font.BOLD,18));//center 聊天消息框 發送消息操作面板jta.setEditable(false);//消息顯示框是不能編輯的jTextArea.setEditable(false);BorderLayout bl3 = new BorderLayout();center.setLayout(bl3);FlowLayout fl = new FlowLayout(FlowLayout.LEFT);operPane.setLayout(fl);operPane.add(input);operPane.add(jtf);operPane.add(jbt);operPane.add(jbt1);center.add(js,BorderLayout.CENTER);//js是消息展示框JScrollPanecenter.add(operPane,BorderLayout.SOUTH);add(center,BorderLayout.CENTER);js.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);//需要時才顯示滾動條//鼠標事件,點擊jbt.addActionListener(this);jbt1.addActionListener(this);this.setDefaultCloseOperation(EXIT_ON_CLOSE);//this.setAlwaysOnTop(true);nickName = JOptionPane.showInputDialog("用戶名:");this.setTitle(nickName + "的聊天室");this.setSize(700,400);this.setVisible(true);Socket s = new Socket("127.0.0.1", 9999);br = new BufferedReader(new InputStreamReader(s.getInputStream()));ps = new PrintStream(s.getOutputStream());new Thread(this).start();//run()ps.println("LOGIN#" + nickName);//發送登錄信息,消息格式:LOGIN#nickNamejtf.setFocusable(true);//設置焦點//鍵盤事件,實現當輸完要發送的內容后,直接按回車鍵,實現發送//監聽鍵盤相應的控件必須是獲得焦點(focus)的情況下才能起作用jtf.addKeyListener(new KeyAdapter() {@Overridepublic void keyPressed(KeyEvent e) {if(e.getKeyCode() == KeyEvent.VK_ENTER) {ps.println("MSG#" + nickName + "#" + jtf.getText());//發送消息的格式:MSG#nickName#message//發送完后,是輸入框中內容為空jtf.setText("");}}});//私聊消息框按回車發送消息jTextField.addKeyListener(new KeyAdapter() {@Overridepublic void keyPressed(KeyEvent e) {if(e.getKeyCode() == KeyEvent.VK_ENTER) {handleSS();}}});//監聽系統關閉事件,退出時給服務器端發出指定消息this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {ps.println("OFFLINE#" + nickName);//發送下線信息,消息格式:OFFLINE#nickName}});this.addComponentListener(new ComponentAdapter() {//監聽父窗口大小的改變 public void componentMoved(ComponentEvent e) {Component comp = e.getComponent(); MAIN_FRAME_LOC_X = comp.getX();MAIN_FRAME_LOC_Y = comp.getY(); }}); }public void run(){//客戶端與服務器端發消息的線程while (true){try{String msg = br.readLine();//讀取服務器是否發送了消息給該客戶端String[] strs = msg.split("#");//判斷是否為服務器發來的登陸信息if(strs[0].equals("LOGIN")){if(!strs[1].equals(nickName)){//不是本人的上線消息就顯示,本人的不顯示jta.append(strs[1] + "上線啦!\n");dl.addElement(strs[1]);//DefaultListModel來更改JList的內容userList.repaint();}}else if(strs[0].equals("MSG")){//接到服務器發送消息的信息if(!strs[1].equals(nickName)){//別人說的jta.append(strs[1] + "說:" + strs[2] + "\n");}else{jta.append("我說:" + strs[2] + "\n");}}else if(strs[0].equals("USERS")){//USER消息,為新建立的客戶端更新好友列表dl.addElement(strs[1]);userList.repaint();} else if(strs[0].equals("ALL")){jta.append("系統消息:" + strs[1] + "\n");}else if(strs[0].equals("OFFLINE")){if(strs[1].equals(nickName)) {//如果是自己下線的消息,說明被服務器端踢出聊天室,強制下線javax.swing.JOptionPane.showMessageDialog(this, "您已被系統請出聊天室!");System.exit(0);}jta.append(strs[1] + "下線啦!\n");dl.removeElement(strs[1]);userList.repaint();}else if((strs[2].equals(nickName) || strs[1].equals(nickName)) && strs[0].equals("SMSG")){if(!strs[1].equals(nickName)){jTextArea.append(strs[1] + "說:" + strs[3] + "\n");jta.append("系統提示:" + strs[1] + "私信了你" + "\n");}else{jTextArea.append("我說:" + strs[3] + "\n");}}else if((strs[2].equals(nickName) || strs[1].equals(nickName)) && strs[0].equals("FSMSG")){sender = strs[1];receiver = strs[2];//接收方第一次收到私聊消息,自動彈出私聊窗口if(!strs[1].equals(nickName)) {FirstSecret = false;jTextArea.append(strs[1] + "說:" + strs[3] + "\n");jta.append("系統提示:" + strs[1] + "私信了你" + "\n");handleSec(strs[1]);}else {jTextArea.append("我說:" + strs[3] + "\n");}}}catch (Exception ex){//如果服務器端出現問題,則客戶端強制下線javax.swing.JOptionPane.showMessageDialog(this, "您已被系統請出聊天室!");System.exit(0);}}}@Overridepublic void actionPerformed(ActionEvent e) {//鼠標點擊事件String label = e.getActionCommand();if(label.equals("發送消息")){//群發handleSend();}else if(label.equals("私發消息") && !userList.isSelectionEmpty()){//未點擊用戶不執行suser = userList.getSelectedValuesList().get(0);//獲得被選擇的用戶handleSec(suser);//創建私聊窗口sender = nickName;receiver = suser;}else if(label.equals("發消息")){handleSS();//私發消息}else if(label.equals("關于本軟件")){JOptionPane.showMessageDialog(this, "1.可以在聊天框中進行群聊\n\n2.可以點擊選擇用戶進行私聊");}else if(label.equals("退出")){JOptionPane.showMessageDialog(this, "您已成功退出!");ps.println("OFFLINE#" + nickName);System.exit(0);} else{System.out.println("不識別的事件");}}public void handleSS(){//在私聊窗口中發消息String name=sender;if(sender.equals(nickName)) {name = receiver;}if(FirstSecret) {ps.println("FSMSG#" + nickName + "#" + name + "#" + jTextField.getText());jTextField.setText(""); FirstSecret = false;}else {ps.println("SMSG#" + nickName + "#" + name + "#" + jTextField.getText());jTextField.setText("");} }public void handleSend(){//群發消息//發送信息時標識一下來源ps.println("MSG#" + nickName + "#" + jtf.getText());//發送完后,是輸入框中內容為空jtf.setText("");}public void handleSec(String name){ //建立私聊窗口JFrame jFrame = new JFrame();//新建了一個窗口 JPanel JPL = new JPanel();JPanel JPL2 = new JPanel();FlowLayout f2 = new FlowLayout(FlowLayout.LEFT);JPL.setLayout(f2);JPL.add(jTextField);JPL.add(jButton);JPL2.add(js1,BorderLayout.CENTER);JPL2.add(JPL,BorderLayout.SOUTH);jFrame.add(JPL2);jButton.addActionListener(this);jTextArea.setFont(new Font("宋體", Font.PLAIN,15));jFrame.setSize(400,310);jFrame.setLocation((int)MAIN_FRAME_LOC_X+20,(int)MAIN_FRAME_LOC_Y+20);//將私聊窗口設置總是在父窗口的中間彈出jFrame.setTitle("與" + name + "私聊中");jFrame.setVisible(true);jTextField.setFocusable(true);//設置焦點jFrame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {jTextArea.setText("");FirstSecret = true;}});}//私聊窗口public static void main(String[] args)throws Exception{new client();} }六、叨叨叨
? ? ? 這個課設的核心應該是線程,消息的格式發送和分解。
? ? ? 其中需要想一下的地方是自動彈出私聊那一部分。需要理清什么時候彈出私聊框,是發送方第一次向接受方發送私聊消息時,因此需要設置一個變量來標記是否是第一次發送消息。其次,因為私聊的接收方發送方的變化,需要設置兩個變量來記錄兩方的昵稱,然后根據客戶自己的nickName來設置消息格式,否則的話私聊的消息格式有一方可能會變成自己發給自己的,這樣另一方就接收不到對方的消息,因而只能單方面發送消息。
總結
以上是生活随笔為你收集整理的用JavaSocket编程开发聊天室,附超详细注释的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试题,反射创建类实例的三种方式是什么
- 下一篇: Java中Class.forName和