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

三步精通 Java Socket 編程:從理解原理到實現即時廣播聊天室

     編輯:Java 相關知識

Socket 編程看似複雜,掌握三步核心邏輯即可入門到精通。本文遵循「理解本質 → 多客戶並發 → 即時廣播」學習路徑,附帶完整可運行代碼,最終構建一個支援暱稱、在線列表、即時廣播的聊天室服務端。

第一步:充分理解 Socket 本質

  • Socket = IP + 端口,通信鏈的句柄
  • JDK 中 Socket (客戶端) / ServerSocket (服務端) 位於 java.net
  • 連接成功雙端各產生一個 Socket 實例,地位完全對等
  • 核心三方法:
    • accept():阻塞等待連接,返回客戶端 Socket
    • getInputStream():獲取輸入流 (對端發送的數據)
    • getOutputStream():獲取輸出流 (發送給對端的數據)
  • 流操作必捕獲 IOException

基礎 Server-Client 模型 (單連接)


// Server
public class SimpleServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket ss = new ServerSocket(10000)) {
            while (true) {
                try (Socket socket = ss.accept();
                     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                     PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
                    String line = in.readLine();
                    out.println("You input: " + line);
                }
            }
        }
    }
}

// Client
public class SimpleClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("localhost", 10000);
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
            
            String line = keyboard.readLine();
            out.println(line);
            System.out.println("Server: " + in.readLine());
        }
    }
}

第二步:多客戶並發連接

設計原理:主線程 accept() 循環接受連接,每個連接交給獨立線程處理,主線程立即回去接受下一個。

public class ConcurrentServer extends ServerSocket {
    public ConcurrentServer() throws IOException { super(10000); }

    public static void main(String[] args) throws IOException {
        new ConcurrentServer().runServer();
    }

    void runServer() throws IOException {
        try {
            while (true) {
                Socket socket = accept();
                new ClientHandler(socket).start(); // 關鍵:啟動新線程
            }
        } finally { close(); }
    }

    static class ClientHandler extends Thread {
        private final Socket client;
        private BufferedReader in;
        private PrintWriter out;

        ClientHandler(Socket s) throws IOException {
            this.client = s;
            this.in = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
            this.out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true);
            out.println("--- Welcome ---");
        }

        @Override public void run() {
            try {
                String line = in.readLine();
                while (line != null && !"bye".equalsIgnoreCase(line)) {
                    String msg = processMessage(line); // 業務處理
                    out.println(msg);
                    line = in.readLine();
                }
                out.println("--- See you, bye! ---");
            } catch (IOException e) {
                // log
            } finally {
                try { client.close(); } catch (IOException ignored) {}
            }
        }

        String processMessage(String line) { return "Echo: " + line; }
    }
}

第三步:實現信息共享 —— 即時廣播聊天室

核心數據結構:

  • List onlineUsers:在線用戶暱稱
  • List handlers:所有客戶端處理器引用 (用於廣播)
  • Queue messageQueue:待廣播消息隊列
  • BroadcastThread:專門消費隊列並推送給所有客戶端
public class ChatServer extends ServerSocket {
    private static final List ONLINE_USERS = new CopyOnWriteArrayList<>();
    private static final List HANDLERS = new CopyOnWriteArrayList<>();
    private static final Queue MSG_QUEUE = new ConcurrentLinkedQueue<>();
    private static final AtomicInteger COUNTER = new AtomicInteger();

    public ChatServer() throws IOException { super(10000); }

    public static void main(String[] args) throws IOException {
        new ChatServer().runServer();
    }

    void runServer() throws IOException {
        new BroadcastThread().start(); // 啟動廣播線程
        try {
            while (true) {
                Socket socket = accept();
                new ClientHandler(socket).start();
            }
        } finally { close(); }
    }

    // 廣播線程:消費隊列,推送給所有客戶端
    static class BroadcastThread extends Thread {
        @Override public void run() {
            while (true) {
                String msg = MSG_QUEUE.poll();
                if (msg != null) {
                    for (ClientHandler h : HANDLERS) {
                        h.sendMessage(msg);
                    }
                } else {
                    try { Thread.sleep(10); } catch (InterruptedException ignored) {}
                }
            }
        }
    }

    static class ClientHandler extends Thread {
        private final Socket client;
        private BufferedReader in;
        private PrintWriter out;
        private String username;

        ClientHandler(Socket s) throws IOException {
            this.client = s;
            this.in = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
            this.out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true);
            out.println("--- Welcome to Chatroom ---");
            out.println("Input your nickname:");
        }

        void sendMessage(String msg) { out.println(msg); }

        @Override public void run() {
            try {
                String line = in.readLine();
                int flag = 0;
                while (line != null && !"bye".equalsIgnoreCase(line)) {
                    if ("l".equals(line)) { // 列表命令
                        out.println(listOnlineUsers());
                    } else if (flag++ == 0) { // 首條消息作為暱稱
                        username = line;
                        ONLINE_USERS.add(username);
                        HANDLERS.add(this);
                        broadcast("[" + username + " come on in]");
                        out.println(listOnlineUsers());
                    } else {
                        broadcast("<" + username + ">" + line);
                    }
                    line = in.readLine();
                }
            } catch (IOException e) {
                // log
            } finally {
                cleanup();
            }
        }

        void broadcast(String msg) { MSG_QUEUE.offer(msg); }

        String listOnlineUsers() {
            StringBuilder sb = new StringBuilder("-+- Online List -+-\r\n");
            for (String u : ONLINE_USERS) sb.append("[").append(u).append("]\r\n");
            sb.append("-+---------------------+-");
            return sb.toString();
        }

        void cleanup() {
            if (username != null) {
                ONLINE_USERS.remove(username);
                HANDLERS.remove(this);
                broadcast("[" + username + " left]");
            }
            try { client.close(); } catch (IOException ignored) {}
        }
    }
}

關鍵設計亮點

設計作用技術點
CopyOnWriteArrayList線程安全遍歷,無需加鎖廣播時遍歷 HANDLERS,寫入較少讀取極多場景最優
ConcurrentLinkedQueue無鎖隊列,生產者(ClientHandler) / 消費者(BroadcastThread) 分離避免客戶端線程阻塞於發送,解耦接收與廣播
專用廣播線程統一發送邏輯,減少鎖競爭單線程序列化發送,簡化併發控制
暱稱註冊機制首條消息作暱稱,後續消息帶標識簡單協議設計,無需複雜登錄流程
清理機制斷開時移除列表、廣播下線通知finally 塊保證執行

客戶端測試

public class ChatClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("localhost", 10000);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
             BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
            
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println(line); // 顯示服務端消息
                if (line.contains("nickname")) {
                    System.out.print("> ");
                    String nick = keyboard.readLine();
                    out.println(nick);
                } else {
                    System.out.print("> ");
                    String input = keyboard.readLine();
                    if (input != null) out.println(input);
                    if ("bye".equalsIgnoreCase(input)) break;
                }
            }
        }
    }
}

總結

三步精通 Socket:

  1. 理解本質:Socket = 端點句柄,三大核心方法 (accept, getInputStream, getOutputStream)
  2. 多客戶並發:主線程 accept 循環 + 每連接一線程/線程池處理業務
  3. 信息共享:共享數據結構 (用戶列表、處理器引用、消息隊列) + 專用廣播線程

此聊天室雖簡單,但包含了服務端架構的靈魂:接收、處理、路由、廣播、狀態管理、生命週期。理解此模型,再學 Netty、gRPC、WebSocket 皆為變體應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved