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

Java 網絡編程基礎 (四):ServerSocket 構建 TCP 服務端——從單線程到線程池模型

     編輯:Java 相關知識

ServerSocket 是 TCP 服務端的聽眾,負責綁定端口、監聽連接請求、通過 accept() 產生客戶端專屬 Socket。本文演進展示:單線程 → 多線程 → 線程池 → 生產級優雅關閉,附帶完整可運行代碼。

核心構造器

構造器參數用途
ServerSocket(int port)端口號綁定所有網卡 (0.0.0.0)
ServerSocket(int port, int backlog)端口 + 等待隊列長度高併發調大 backlog (默認 50,建議 1024+)
ServerSocket(int port, int backlog, InetAddress bindAddr)端口 + 隊列 + 綁定地址多網卡指定綁定 (如只綁內網 IP)

演進 1:單線程阻塞模型 (教學用)

public class ServerV1_SingleThread {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(10000)) {
            System.out.println("Server V1 啟動,端口 10000");
            while (true) {
                try (Socket client = server.accept();
                     BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
                     PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {
                    
                    System.out.println("客戶端連接: " + client.getInetAddress());
                    String cmd;
                    while ((cmd = in.readLine()) != null) {
                        cmd = cmd.toUpperCase();
                        if (cmd.startsWith("BYE")) break;
                        if (cmd.startsWith("DATE") || cmd.startsWith("TIME")) out.println(new Date());
                        else if (cmd.startsWith("DOM")) out.println(LocalDate.now().getDayOfMonth());
                        else if (cmd.startsWith("DOW")) out.println(DayOfWeek.from(LocalDate.now()));
                        else if (cmd.startsWith("DOY")) out.println(LocalDate.now().getDayOfYear());
                        else if (cmd.startsWith("PAUSE")) { Thread.sleep(3000); out.println("Paused 3s"); }
                        else out.println("Unknown: " + cmd);
                    }
                } catch (IOException | InterruptedException e) {
                    System.err.println("客戶端異常: " + e.getMessage());
                }
            }
        }
    }
}

缺陷:同一時間只能服務一個客戶端,其他連接排隊阻塞於 accept()。

演進 2:每連接一線程模型

public class ServerV2_ThreadPerClient {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(10000)) {
            System.out.println("Server V2 啟動 (每連接一線程)");
            while (true) {
                Socket client = server.accept();
                new Thread(new ClientHandler(client), "Client-" + client.getPort()).start();
            }
        }
    }
    
    static class ClientHandler implements Runnable {
        private final Socket socket;
        ClientHandler(Socket socket) { this.socket = socket; }
        
        @Override public void run() {
            String clientInfo = socket.getInetAddress() + ":" + socket.getPort();
            try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                 PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
                
                System.out.println("處理客戶端: " + clientInfo);
                String cmd;
                while ((cmd = in.readLine()) != null) {
                    cmd = cmd.toUpperCase();
                    if (cmd.startsWith("BYE")) break;
                    // ... 業務邏輯同 V1
                }
            } catch (IOException e) {
                System.err.println("客戶端 " + clientInfo + " 異常: " + e.getMessage());
            } finally {
                try { socket.close(); } catch (IOException ignored) {}
            }
        }
    }
}

缺陷:連接數大時線程爆炸,OOM、上下文切換開銷巨大。

演進 3:固定線程池模型 (生產雛形)

public class ServerV3_ThreadPool {
    private static final ExecutorService POOL = Executors.newFixedThreadPool(100);
    private static final AtomicBoolean RUNNING = new AtomicBoolean(true);

    public static void main(String[] args) throws IOException {
        // 優雅關閉鉤子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            RUNNING.set(false);
            POOL.shutdown();
            try { POOL.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException ignored) {}
        }));

        try (ServerSocket server = new ServerSocket(10000)) {
            server.setSoTimeout(1000); // accept 超時,定期檢查 RUNNING
            System.out.println("Server V3 啟動 (線程池 100)");
            while (RUNNING.get()) {
                try {
                    Socket client = server.accept();
                    POOL.submit(new ClientHandler(client));
                } catch (SocketTimeoutException ignored) {
                    // 定期檢查 RUNNING 標誌
                } catch (IOException e) {
                    if (RUNNING.get()) System.err.println("Accept 異常: " + e.getMessage());
                }
            }
        } finally {
            POOL.shutdownNow();
        }
    }
    // ClientHandler 同 V2
}

關鍵最佳實踐總結

要點做法理由
Backlog 設置new ServerSocket(port, 1024)高併發下避免連接請求被拒 (TCP SYN 隊列滿)
線程池隔離固定大小 + 有界隊列 (ThreadPoolExecutor)防止線程無限增長,拒絕策略保護系統
accept 超時server.setSoTimeout(1000)讓主循環可檢查關閉標誌,實現優雅關閉
關閉鉤子Runtime.addShutdownHookJVM 關閉時 (Ctrl+C, kill, 容器停止) 觸發優雅停機
編碼/緩衝/自動刷新BufferedReader/PrintWriter(UTF-8, true)文本協議標配,避免亂碼/粘包/延遲
異常隔離ClientHandler 內部 try-catch單客戶端異常不影響主線程/其他客戶端
資源釋放try-with-resources / finally close防止文件描述符洩漏

生產級替代:Netty


// Netty 服務端啟動 (事件驅動、非阻塞、零拷貝)
ServerBootstrap b = new ServerBootstrap();
b.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
 .channel(NioServerSocketChannel.class)
 .option(ChannelOption.SO_BACKLOG, 1024)
 .childOption(ChannelOption.TCP_NODELAY, true)
 .childHandler(new ChannelInitializer() {
     @Override protected void initChannel(SocketChannel ch) {
         ch.pipeline()
           .addLast(new LoggingHandler(LogLevel.INFO))
           .addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
           .addLast(new StringDecoder(StandardCharsets.UTF_8))
           .addLast(new StringEncoder(StandardCharsets.UTF_8))
           .addLast(new BusinessHandler()); // 你的業務處理器
     }
 });
b.bind(10000).sync().channel().closeFuture().sync();

總結

ServerSocket 服務端演進路徑:單線程 → 每連接一線程 → 固定線程池 + 優雅關閉 → Netty 事件驅動。掌握 accept() 循環、線程池隔離、setSoTimeout 配合關閉標誌、ShutdownHook 優雅停機,即可構建健壯 TCP 服務。高併發/長連接場景,直接上 Netty。

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