首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java Socket 廣播信使實戰:多線程 C/S 架構完整實現

     編輯:Java 程式設計入門

項目目標

構建一個 **局域網廣播聊天/通知系統**:

  • **Server**:單端口監聽,維護所有客戶端連接,收到任一客戶端消息 → **廣播** 給所有在線客戶端
  • **Client**:GUI 界面,輸入用戶名登錄,發送/接收廣播消息,顯示在線用戶
  • **核心技術**:`ServerSocket`/`Socket`、多線程 (`Thread`/`Runnable`)、IO 流 (`DataInputStream`/`DataOutputStream`)、Swing GUI

架構設計


+----------------+          TCP/Socket          +-----------------+
|   Client A     | ◄──────────────────────────► |                 |
|   Client B     | ◄──────────────────────────► |   ChatServer    |  (維護 List)
|   Client C     | ◄──────────────────────────► |                 |
+----------------+                              +-----------------+
        ▲                                                │
        │              廣播循環: for(h: handlers) h.send(msg) │
        └────────────────────────────────────────────────────┘

服務端核心代碼


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer extends JFrame {
    private final JTextArea logArea = new JTextArea(20, 50);
    private final List clients = Collections.synchronizedList(new ArrayList<>());
    private ServerSocket serverSocket;

    public ChatServer(int port) throws IOException {
        super("廣播信使 - 服務端 [" + port + "]");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        logArea.setEditable(false);
        logArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
        add(new JScrollPane(logArea), BorderLayout.CENTER);
        pack(); setLocationRelativeTo(null); setVisible(true);

        serverSocket = new ServerSocket(port);
        log("服務器啟動,監聽端口 " + port);

        new Thread(this::acceptLoop).start();
    }

    private void acceptLoop() {
        try {
            while (!serverSocket.isClosed()) {
                Socket client = serverSocket.accept();
                ClientHandler handler = new ClientHandler(client);
                clients.add(handler);
                handler.start();
                log("新連接: " + client.getInetAddress() + " 當前在線: " + clients.size());
            }
        } catch (IOException e) {
            if (!serverSocket.isClosed()) log("Accept 錯誤: " + e);
        }
    }

    // 廣播給所有客戶端
    void broadcast(String msg, ClientHandler sender) {
        String full = "[" + sender.getUserName() + "] " + msg;
        log(full);
        synchronized (clients) {
            for (ClientHandler c : clients) c.send(full);
        }
    }

    // 移除斷開連接的客戶端
    void removeClient(ClientHandler h) {
        clients.remove(h);
        log(h.getUserName() + " 斷開,剩餘: " + clients.size());
    }

    private void log(String s) {
        SwingUtilities.invokeLater(() -> logArea.append(s + "\n"));
    }

    public static void main(String[] args) throws IOException {
        int port = args.length > 0 ? Integer.parseInt(args[0]) : 8888;
        new ChatServer(port);
    }

    // ===== 內部類:處理單個客戶端 =====
    class ClientHandler extends Thread {
        private final Socket socket;
        private DataInputStream in;
        private DataOutputStream out;
        private String userName;

        ClientHandler(Socket s) { this.socket = s; }

        @Override public void run() {
            try {
                in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
                out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

                // 首個數據包為用戶名
                userName = in.readUTF();
                broadcast(userName + " 上線了", this);

                String msg;
                while ((msg = in.readUTF()) != null) {
                    broadcast(msg, this);
                }
            } catch (IOException e) {
                // 正常斷開或異常
            } finally {
                close();
                removeClient(this);
            }
        }

        void send(String msg) {
            try { out.writeUTF(msg); out.flush(); } catch (IOException ignored) {}
        }

        String getUserName() { return userName != null ? userName : socket.getInetAddress().toString(); }

        void close() {
            try { socket.close(); } catch (IOException ignored) {}
        }
    }
}

客戶端核心代碼


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class ChatClient extends JFrame implements Runnable {
    private final JTextArea chatArea = new JTextArea(15, 40);
    private final JTextField inputField = new JTextField(30);
    private final DefaultListModel userListModel = new DefaultListModel<>();
    private final JList userList = new JList<>(userListModel);
    private DataInputStream in;
    private DataOutputStream out;
    private Socket socket;
    private String userName;

    public ChatClient(String host, int port) throws IOException {
        super("廣播信使 - 客戶端");
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // 登錄對話框
        userName = JOptionPane.showInputDialog(this, "輸入用戶名:", "登錄", JOptionPane.PLAIN_MESSAGE);
        if (userName == null || userName.trim().isEmpty()) System.exit(0);
        userName = userName.trim();

        // 佈局
        chatArea.setEditable(false);
        chatArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
        JPanel center = new JPanel(new BorderLayout());
        center.add(new JScrollPane(chatArea), BorderLayout.CENTER);
        center.add(new JScrollPane(userList), BorderLayout.EAST);
        userList.setPreferredSize(new Dimension(120, 0));
        userList.setBorder(BorderFactory.createTitledBorder("在線用戶"));

        JPanel bottom = new JPanel(new BorderLayout());
        bottom.add(new JLabel(userName + ": "), BorderLayout.WEST);
        bottom.add(inputField, BorderLayout.CENTER);
        JButton sendBtn = new JButton("發送");
        bottom.add(sendBtn, BorderLayout.EAST);

        add(center, BorderLayout.CENTER);
        add(bottom, BorderLayout.SOUTH);
        pack(); setLocationRelativeTo(null); setVisible(true);

        // 連接服務器
        socket = new Socket(host, port);
        in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
        out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

        // 發送用戶名
        out.writeUTF(userName);
        out.flush();

        // 發送按鈕事件
        ActionListener sendAction = e -> sendMessage();
        inputField.addActionListener(sendAction);
        sendBtn.addActionListener(sendAction);

        // 啟動接收線程
        new Thread(this).start();
    }

    private void sendMessage() {
        String text = inputField.getText().trim();
        if (text.isEmpty()) return;
        try { out.writeUTF(text); out.flush(); inputField.setText(""); }
        catch (IOException ex) { append("發送失敗: " + ex); }
    }

    @Override public void run() {
        try {
            String msg;
            while ((msg = in.readUTF()) != null) {
                // 簡單協議: "用戶列表:|user1|user2|" 或 普通消息
                if (msg.startsWith("用戶列表:")) {
                    updateUserList(msg.substring(5));
                } else {
                    append(msg);
                }
            }
        } catch (IOException e) {
            append("連接斷開: " + e);
        } finally { close(); }
    }

    private void append(String s) {
        SwingUtilities.invokeLater(() -> chatArea.append(s + "\n"));
    }

    private void updateUserList(String data) {
        SwingUtilities.invokeLater(() -> {
            userListModel.clear();
            for (String u : data.split("\\|")) if (!u.isEmpty()) userListModel.addElement(u);
        });
    }

    void close() {
        try { socket.close(); } catch (IOException ignored) {}
        System.exit(0);
    }

    public static void main(String[] args) throws IOException {
        String host = args.length > 0 ? args[0] : "127.0.0.1";
        int port = args.length > 1 ? Integer.parseInt(args[1]) : 8888;
        SwingUtilities.invokeLater(() -> {
            try { new ChatClient(host, port); } catch (IOException ex) { ex.printStackTrace(); }
        });
    }
}

運行步驟

  1. 編譯:`javac ChatServer.java ChatClient.java`
  2. 啟動服務端:`java ChatServer 8888`
  3. 啟動多個客戶端:`java ChatClient 127.0.0.1 8888` (分別輸入不同用戶名)
  4. 任一客戶端發送消息,所有客戶端實時收到

關鍵技術點總結

模塊關鍵類/方法作用
服務端監聽`ServerSocket.accept()`阻塞等待客戶端連接,返回 `Socket`
多線程隔離`ClientHandler extends Thread`每連接一線程,互不阻塞
數據流`DataInputStream`/`DataOutputStream` + `readUTF`/`writeUTF`處理字符串編碼/長度前綴,避免粘包
線程安全集合`Collections.synchronizedList`多線程並發增刪客戶端列表
UI 線程隔離`SwingUtilities.invokeLater`網絡線程安全更新 Swing 組件
廣播模式遍歷 handlers 逐個 `send()`單點發送 → 服務端扇出 → 所有客戶端

生產級擴展建議

  1. 心跳檢測:定時發送 `PING`,超時移除僵屍連接
  2. 消息協議升級:JSON/Protobuf 支持私聊、群組、文件傳輸、表情
  3. NIO 非阻塞:`Selector` + `SocketChannel` 支持萬級並發 (Netty 框架)
  4. 安全性:SSL/TLS 加密傳輸、用戶認證、權限控制
  5. 持久化:離線消息存儲、聊天記錄數據庫
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved