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

Java 網路程式設計基礎(上):ServerSocket 與多執行緒 TCP 伺服器實作

     編輯:Java 相關知識

TCP 伺服器核心流程

  1. 建立 ServerSocket 綁定監聽埠 (如 10000)。
  2. 無限迴圈 呼叫 accept() 阻塞等待客戶端連線。
  3. accept() 返回新的 Socket 代表該連線,綁定客戶端 IP/埠。
  4. 啟動新執行緒 處理該 Socket I/O,主執行緒立即回去 accept() 下一個連線。

完整範例:日期時間查詢伺服器

SSServer.java

import java.io.*;
import java.net.*;
import java.util.*;

public class SSServer {
    public static void main(String[] args) throws IOException {
        System.out.println("Server starting... (Port 10000)");
        try (ServerSocket server = new ServerSocket(10000)) {
            while (true) {
                Socket client = server.accept(); // 阻塞直到連線
                System.out.println("Accepting connection from " + client.getInetAddress());
                // 為每個客戶端啟動獨立執行緒
                new Thread(new ServerHandler(client)).start();
            }
        }
    }
}

class ServerHandler implements Runnable {
    private final Socket socket;

    ServerHandler(Socket socket) { this.socket = socket; }

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

            Calendar cal = Calendar.getInstance();
            String cmd;

            // 讀取命令迴圈
            while ((cmd = in.readLine()) != null) {
                cmd = cmd.toUpperCase().trim();
                if (cmd.startsWith("BYE")) break;

                switch (cmd) {
                    case "DATE":
                    case "TIME":
                        out.println(cal.getTime());
                        break;
                    case "DOM":
                        out.println(cal.get(Calendar.DAY_OF_MONTH));
                        break;
                    case "DOW":
                        String[] days = {"", "SUNDAY","MONDAY","TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","SATURDAY"};
                        out.println(days[cal.get(Calendar.DAY_OF_WEEK)]);
                        break;
                    case "DOY":
                        out.println(cal.get(Calendar.DAY_OF_YEAR));
                        break;
                    case "PAUSE":
                        try { Thread.sleep(3000); } catch (InterruptedException ignored) {}
                        out.println("Paused 3 seconds.");
                        break;
                    default:
                        out.println("Unknown command: " + cmd);
                }
            }
        } catch (IOException e) {
            System.err.println("Connection error: " + e.getMessage());
        } finally {
            System.out.println("Closing connection from " + socket.getInetAddress());
            try { socket.close(); } catch (IOException ignored) {}
        }
    }
}

SSClient.java (簡易測試客戶端)

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class SSClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("localhost", 10000);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             Scanner scanner = new Scanner(System.in)) {

            System.out.println("Connected. Commands: DATE, TIME, DOM, DOW, DOY, PAUSE, BYE");
            String input;
            while (true) {
                System.out.print("> ");
                input = scanner.nextLine();
                out.println(input);
                if (input.trim().equalsIgnoreCase("BYE")) break;
                String response = in.readLine();
                System.out.println("Server: " + response);
            }
        }
    }
}

關鍵設計模式與最佳實務

1. 執行緒池替代無限建立執行緒

// 固定大小執行緒池,避免連線風暴耗盡記憶體
ExecutorService pool = Executors.newFixedThreadPool(50); // 或用 ThreadPoolExecutor 自訂

while (true) {
    Socket client = server.accept();
    pool.execute(new ServerHandler(client)); // 提交任務而非 new Thread()
}

2. NIO 非阻塞模型 (高併發必學)

傳統 BIO (Blocking I/O):一連線一執行緒,C10K 問題。
Java NIO (java.nio.channels):單執行緒透過 Selector 多路複用管理成千上萬連線。
現代框架 Netty 封裝 NIO 細節,提供事件驅動架構,是高效能伺服器標準選擇。

3. 緩衝區與編碼處理

  • 一定要指定 Charset:new InputStreamReader(is, StandardCharsets.UTF_8),避免平台預設編碼不一致導致亂碼。
  • BufferedReader.readLine() 處理換行符 (\n, \r\n, \r) 跨平台相容。
  • PrintWriter(String, true) 第二參數 autoFlush=true,println 會自動刷新,適合行導向協議。

4. 關閉資源順序與異常安全

使用 try-with-resources (JDK 7+) 自動按相反順序關閉,即使發生異常也保證釋放:

try (Socket socket = server.accept();
     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
     PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
    // 業務邏輯
} // 自動關閉 out, in, socket

ServerSocket 進階配置

方法用途
setSoTimeout(ms)accept() 超時,定期檢查關閉旗標優雅關閉
setReuseAddress(true)重啟伺服器時避免 "Address already in use" (TIME_WAIT 狀態)
setReceiveBufferSize(bytes)調整接收緩衝區 (OS 層面),大流量可適當調大
getInetAddress()取得綁定的本地 IP (多網卡環境可指定綁定特定 IP)

從 BIO 到 NIO 到 Netty 的演進路線

BIO (Thread-per-Connection)
    ↓ 連線數增長 → 線程上下文切換開銷大、記憶體爆炸
NIO (Selector + Channel + Buffer)
    ↓ API 複雜、需處理半包/粘包、Selector 空輪詢 Bug (JDK 1.6 修復)
Netty (EventLoop + Pipeline + Codec)
    ↓ 生產級框架:零拷貝、內存池、流控、SSL、多協議、成熟生態

理解 BIO 基礎是掌握 NIO/Netty 的前提。建議先手寫簡易 BIO 伺服器,再重構為 NIO,最後遷移到 Netty,體會每層抽象解決的核心問題。

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