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

Java Web 中 Socket 多線程長連接通訊完整實現

     編輯:Java 相關知識

架構設計

  • ServletContextListener:應用啟動時啟動 Socket 服務端線程,關閉時優雅停止。
  • ServerSocket 接收線程:持續 accept() 客戶端連接,每連接啟動一個處理線程。
  • 業務處理線程:讀取輸入流、解析協議、執行邏輯、寫回響應,支持長連接心跳。

核心代碼實現

1. 啟動監聽器

@WebListener
public class SocketServerListener implements ServletContextListener {
    private SocketServerThread serverThread;
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        serverThread = new SocketServerThread(8888); // 端口配置化
        serverThread.start();
        sce.getServletContext().log("Socket 服務已啟動在端口 8888");
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (serverThread != null) {
            serverThread.shutdown();
            sce.getServletContext().log("Socket 服務已停止");
        }
    }
}

2. ServerSocket 接收線程

public class SocketServerThread extends Thread {
    private final int port;
    private ServerSocket serverSocket;
    private volatile boolean running = true;
    private final ExecutorService workerPool = Executors.newCachedThreadPool();
    
    public SocketServerThread(int port) { this.port = port; }
    
    @Override
    public void run() {
        try {
            serverSocket = new ServerSocket(port);
            serverSocket.setSoTimeout(1000); // 定期檢查 running 標誌
            while (running) {
                try {
                    Socket client = serverSocket.accept();
                    client.setSoTimeout(30000); // 讀超時
                    workerPool.submit(new ClientHandler(client));
                } catch (SocketTimeoutException ignored) { /* 定期檢查 running */ }
            }
        } catch (IOException e) {
            if (running) log.error("ServerSocket 異常", e);
        } finally {
            shutdownPool();
        }
    }
    
    public void shutdown() {
        running = false;
        try { if (serverSocket != null) serverSocket.close(); } catch (IOException ignored) {}
    }
    
    private void shutdownPool() {
        workerPool.shutdown();
        try { if (!workerPool.awaitTermination(5, TimeUnit.SECONDS)) workerPool.shutdownNow(); } 
        catch (InterruptedException e) { workerPool.shutdownNow(); }
    }
}

3. 客戶端處理器 (長連接 + 協議解析)

public class ClientHandler implements Runnable {
    private final Socket socket;
    private final BufferedReader in;
    private final BufferedWriter out;
    private volatile boolean connected = true;
    
    public ClientHandler(Socket socket) throws IOException {
        this.socket = socket;
        this.in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
        this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
    }
    
    @Override
    public void run() {
        try {
            String line;
            while (connected && (line = in.readLine()) != null) {
                if (line.trim().isEmpty()) continue;
                
                // 簡單協議: CMD,param1,param2,...\n
                String[] parts = line.split(",", 2);
                String cmd = parts[0].trim();
                String payload = parts.length > 1 ? parts[1].trim() : "";
                
                String response = processCommand(cmd, payload);
                sendResponse(response);
            }
        } catch (IOException e) {
            log.warn("客戶端連接異常: {}", e.getMessage());
        } finally {
            closeResources();
        }
    }
    
    private String processCommand(String cmd, String payload) {
        switch (cmd) {
            case "BEAT": return "RES,OK,HEARTBEAT\n"; // 心跳
            case "DATA": return handleData(payload);   // 業務數據
            case "QUIT": connected = false; return "RES,OK,BYE\n";
            default: return "ERR,UNKNOWN_CMD\n";
        }
    }
    
    private String handleData(String payload) {
        // 實際業務邏輯:解析 payload、調用 Service、構建響應
        return "RES,OK,RECEIVED:" + payload + "\n";
    }
    
    private void sendResponse(String resp) {
        try { out.write(resp); out.flush(); } catch (IOException e) { connected = false; }
    }
    
    private void closeResources() {
        try { in.close(); } catch (IOException ignored) {}
        try { out.close(); } catch (IOException ignored) {}
        try { socket.close(); } catch (IOException ignored) {}
    }
}

4. 客戶端測試代碼

public class SocketClientTest {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 8888);
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"))) {
            
            // 發送心跳
            out.println("BEAT");
            System.out.println("Server: " + in.readLine());
            
            // 發送數據
            out.println("DATA,溫度:25.6,濕度:60%");
            System.out.println("Server: " + in.readLine());
            
            // 退出
            out.println("QUIT");
            System.out.println("Server: " + in.readLine());
        }
    }
}

生產環境關鍵優化

技術選型建議

  • 簡單場景:上述原生實現足夠。
  • 高併發/長連接/複雜協議:直接用 Netty,事件驅動、零拷貝、內建心跳/解碼/編解碼器,避免重複造輪子。
  • WebSocket 需求:Spring WebSocket / Jetty / Tomcat 原生 WebSocket 支持,而非自建 TCP。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
問題解決方案
線程池耗盡使用有界隊列 ThreadPoolExecutor、拒絕策略、監控指標
半包/粘包定義明確協議分隔符(\n)、長度前綴、或使用 Netty 內建解碼器
連接洩漏finally 塊確保關閉、定期清理空閒連接、啟用 TCP KeepAlive
編碼亂碼統一 UTF-8、顯式指定 Charset、避免平台默認編碼
優雅關閉ShutdownHook 關閉 ServerSocket、中斷工作線程、等待任務完成