目錄
案例要求:
實現思路:
itheima-chat-server包
src
com.itheima
Constant類:
Server類:
ServerReaderThread類:
itheima-chat-system包
src
com.itheima.ui
ChatEntryFrame類:
ClientChatFrame類:
ClientReaderThread類:
Constant類:
APP啟動類:
總結:
案例要求:
實現思路:
itheima-chat-server包
src
com.itheima
Constant類:
package com.itheima;public class Constant {public static final int PORT = 6666;
}
Server類:
package com.itheima;import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;public class Server {// 定義一個集合容器存儲所有登錄進來的客戶端管道,以便將來群發消息給他們.// 定義一個Map集合,鍵是存儲客戶端的管道,值是這個管道的用戶名稱。public static final Map<Socket, String> onLineSockets = new HashMap<>();public static void main(String[] args) {System.out.println("啟動服務端系統.....");try {// 1、注冊端口。ServerSocket serverSocket = new ServerSocket(Constant.PORT);// 2、主線程負責接受客戶端的連接請求while (true) {// 3、調用accept方法,獲取到客戶端的Socket對象System.out.println("等待客戶端的連接.....");Socket socket = serverSocket.accept();// 把這個管道交給一個獨立的線程來處理:以便支持很多客戶端可以同時進來通信。new ServerReaderThread(socket).start();System.out.println("一個客戶端連接成功.....");}} catch (Exception e) {e.printStackTrace();}}
}
ServerReaderThread類:
package com.itheima;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 接收的消息可能有很多種類型:1、登錄消息(包含昵稱) 2、群聊消息 3、私聊消息// 所以客戶端必須聲明協議發送消息// 比如客戶端先發1,代表接下來是登錄消息。// 比如客戶端先發2,代表接下來是群聊消息。// 先從socket管道中接收客戶端發送來的消息類型編號DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1、2、3switch (type){case 1:// 客戶端發來了登錄消息,接下來要接收昵稱數據,再更新全部在線客戶端的在線人數列表。String nickname = dis.readUTF();// 把這個登錄成功的客戶端socket存入到在線集合。Server.onLineSockets.put(socket, nickname);// 更新全部客戶端的在線人數列表updateClientOnLineUserList();break;case 2:// 客戶端發來了群聊消息,接下來要接收群聊消息內容,再把群聊消息轉發給全部在線客戶端。String msg = dis.readUTF();sendMsgToAll(msg);break;case 3:// 客戶端發來了私聊消息,接下來要接收私聊消息內容,再把私聊消息轉發給指定客戶端。break;}}} catch (Exception e) {System.out.println("客戶端下線了:"+ socket.getInetAddress().getHostAddress());Server.onLineSockets.remove(socket); // 把下線的客戶端socket從在線集合中移除updateClientOnLineUserList(); // 下線了用戶也需要更新全部客戶端的在線人數列表。}}// 給全部在線socket推送當前客戶端發來的消息private void sendMsgToAll(String msg) {// 一定要拼裝好這個消息,再發給全部在線socket.StringBuilder sb = new StringBuilder();String name = Server.onLineSockets.get(socket);// 獲取當前時間LocalDateTime now = LocalDateTime.now();DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dtf.format(now);String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();// 推送給全部客戶端socketfor (Socket socket : Server.onLineSockets.keySet()) {try {// 3、把集合中的所有用戶名稱,通過socket管道發送給客戶端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(2); // 1代表告訴客戶端接下來是在線人數列表信息 2 代表發的是群聊消息dos.writeUTF(msgResult);dos.flush(); // 刷新數據!} catch (Exception e) {e.printStackTrace();}}}private void updateClientOnLineUserList() {// 更新全部客戶端的在線人數列表// 拿到全部在線客戶端的用戶名稱,把這些名稱轉發給全部在線socket管道。// 1、拿到當前全部在線用戶昵稱Collection<String> onLineUsers = Server.onLineSockets.values();// 2、把這個集合中的所有用戶都推送給全部客戶端socket管道。for (Socket socket : Server.onLineSockets.keySet()) {try {// 3、把集合中的所有用戶名稱,通過socket管道發送給客戶端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 1代表告訴客戶端接下來是在線人數列表信息 2 代表發的是群聊消息dos.writeInt(onLineUsers.size()); // 告訴客戶端,接下來要發多少個用戶名稱for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}dos.flush();} catch (Exception e) {e.printStackTrace();}}}
}
itheima-chat-system包
src
com.itheima.ui
ChatEntryFrame類:
package com.itheima.ui;import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;public class ChatEntryFrame extends JFrame {private JTextField nicknameField;private JButton enterButton;private JButton cancelButton;private Socket socket; // 記住當前客戶端系統的通信管道public ChatEntryFrame() {setTitle("局域網聊天室");setSize(350, 150);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setLocationRelativeTo(null);setResizable(false); // 禁止調整大小// 設置背景顏色getContentPane().setBackground(Color.decode("#F0F0F0"));// 創建主面板并設置布局JPanel mainPanel = new JPanel(new BorderLayout());mainPanel.setBackground(Color.decode("#F0F0F0"));add(mainPanel);// 創建頂部面板JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));topPanel.setBackground(Color.decode("#F0F0F0"));// 標簽和文本框JLabel nicknameLabel = new JLabel("昵稱:");nicknameLabel.setFont(new Font("楷體", Font.BOLD, 16));nicknameField = new JTextField(10);nicknameField.setFont(new Font("楷體", Font.PLAIN, 16));nicknameField.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),BorderFactory.createEmptyBorder(5, 5, 5, 5)));topPanel.add(nicknameLabel);topPanel.add(nicknameField);mainPanel.add(topPanel, BorderLayout.NORTH);// 按鈕面板JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));buttonPanel.setBackground(Color.decode("#F0F0F0"));enterButton = new JButton("登錄");enterButton.setFont(new Font("楷體", Font.BOLD, 16));enterButton.setBackground(Color.decode("#007BFF"));enterButton.setForeground(Color.WHITE);enterButton.setBorderPainted(false);enterButton.setFocusPainted(false);cancelButton = new JButton("取消");cancelButton.setFont(new Font("楷體", Font.BOLD, 16));cancelButton.setBackground(Color.decode("#DC3545"));cancelButton.setForeground(Color.WHITE);cancelButton.setBorderPainted(false);cancelButton.setFocusPainted(false);buttonPanel.add(enterButton);buttonPanel.add(cancelButton);mainPanel.add(buttonPanel, BorderLayout.SOUTH);// 添加監聽器enterButton.addActionListener(e -> {String nickname = nicknameField.getText(); // 獲取昵稱nicknameField.setText("");if (!nickname.isEmpty()) {try {login(nickname);// 進入聊天室邏輯: 啟動聊天界面。把昵稱傳給聊天界面。new ClientChatFrame(nickname, socket);this.dispose(); // 關閉登錄窗口} catch (Exception ex) {ex.printStackTrace();}} else {JOptionPane.showMessageDialog(this, "請輸入昵稱!");}});cancelButton.addActionListener(e -> System.exit(0));this.setVisible(true); // 顯示窗口}public void login(String nickname) throws Exception {// 立即發送登錄消息給服務端程序。// 1、創建Socket管道請求與服務端的socket鏈接socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );// 2、立即發送消息類型1 和自己的昵稱給服務端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 消息類型 登錄dos.writeUTF(nickname);dos.flush();}public static void main(String[] args) {new ChatEntryFrame();}
}
ClientChatFrame類:
package com.itheima.ui;import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;public class ChatEntryFrame extends JFrame {private JTextField nicknameField;private JButton enterButton;private JButton cancelButton;private Socket socket; // 記住當前客戶端系統的通信管道public ChatEntryFrame() {setTitle("局域網聊天室");setSize(350, 150);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setLocationRelativeTo(null);setResizable(false); // 禁止調整大小// 設置背景顏色getContentPane().setBackground(Color.decode("#F0F0F0"));// 創建主面板并設置布局JPanel mainPanel = new JPanel(new BorderLayout());mainPanel.setBackground(Color.decode("#F0F0F0"));add(mainPanel);// 創建頂部面板JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));topPanel.setBackground(Color.decode("#F0F0F0"));// 標簽和文本框JLabel nicknameLabel = new JLabel("昵稱:");nicknameLabel.setFont(new Font("楷體", Font.BOLD, 16));nicknameField = new JTextField(10);nicknameField.setFont(new Font("楷體", Font.PLAIN, 16));nicknameField.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),BorderFactory.createEmptyBorder(5, 5, 5, 5)));topPanel.add(nicknameLabel);topPanel.add(nicknameField);mainPanel.add(topPanel, BorderLayout.NORTH);// 按鈕面板JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));buttonPanel.setBackground(Color.decode("#F0F0F0"));enterButton = new JButton("登錄");enterButton.setFont(new Font("楷體", Font.BOLD, 16));enterButton.setBackground(Color.decode("#007BFF"));enterButton.setForeground(Color.WHITE);enterButton.setBorderPainted(false);enterButton.setFocusPainted(false);cancelButton = new JButton("取消");cancelButton.setFont(new Font("楷體", Font.BOLD, 16));cancelButton.setBackground(Color.decode("#DC3545"));cancelButton.setForeground(Color.WHITE);cancelButton.setBorderPainted(false);cancelButton.setFocusPainted(false);buttonPanel.add(enterButton);buttonPanel.add(cancelButton);mainPanel.add(buttonPanel, BorderLayout.SOUTH);// 添加監聽器enterButton.addActionListener(e -> {String nickname = nicknameField.getText(); // 獲取昵稱nicknameField.setText("");if (!nickname.isEmpty()) {try {login(nickname);// 進入聊天室邏輯: 啟動聊天界面。把昵稱傳給聊天界面。new ClientChatFrame(nickname, socket);this.dispose(); // 關閉登錄窗口} catch (Exception ex) {ex.printStackTrace();}} else {JOptionPane.showMessageDialog(this, "請輸入昵稱!");}});cancelButton.addActionListener(e -> System.exit(0));this.setVisible(true); // 顯示窗口}public void login(String nickname) throws Exception {// 立即發送登錄消息給服務端程序。// 1、創建Socket管道請求與服務端的socket鏈接socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );// 2、立即發送消息類型1 和自己的昵稱給服務端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 消息類型 登錄dos.writeUTF(nickname);dos.flush();}public static void main(String[] args) {new ChatEntryFrame();}
}
ClientReaderThread類:
package com.itheima.ui;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class ClientReaderThread extends Thread{private Socket socket;private DataInputStream dis;private ClientChatFrame win;public ClientReaderThread( Socket socket, ClientChatFrame win) {this.win = win;this.socket = socket;}@Overridepublic void run() {try {// 接收的消息可能有很多種類型:1、在線人數更新的數據 2、群聊消息dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1、2、3switch (type){case 1:// 服務端發來的在線人數更新消息updateClientOnLineUserList();break;case 2:// 服務端發送來的群聊消息getMsgToWin();break;}}} catch (Exception e) {e.printStackTrace();}}private void getMsgToWin() throws Exception {// 獲取群聊消息String msg = dis.readUTF();win.setMsgToWin(msg);}// 更新客戶端的在線用戶列表private void updateClientOnLineUserList() throws Exception {// 1、讀取有多少個在線用戶int count = dis.readInt();// 2、循環控制讀取多少個用戶信息。String[] names = new String[count];for (int i = 0; i < count; i++) {// 3、讀取每個用戶的信息String nickname = dis.readUTF();// 4、將每個用戶的信息添加到數組names[i] = nickname;}// 5、將集合中的數據展示到窗口上win.updateOnlineUsers(names);}}
Constant類:
package com.itheima.ui;public class Constant {public static final String SERVER_IP = "127.0.0.1";public static final int SERVER_PORT = 6666;
}
APP啟動類:
import com.itheima.ui.ChatEntryFrame;public class App {public static void main(String[] args) {new ChatEntryFrame(); // 啟動登錄界面}
}