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

Java Socket 網絡編程實戰:從基礎到多線程服務端

     編輯:Java 相關知識

網絡編程兩大核心問題

  1. 主機定位:IP 地址唯一標識網絡主機(IPv4/IPv6)
  2. 可靠傳輸:TCP(面向連接、可靠、有序)vs UDP(無連接、不可靠、高效)

傳輸層協議對比

特性TCPUDP
連接模式面向連接(三次握手/四次揮手)無連接
可靠性可靠、有序、無錯誤不可靠、無序、可能丟包/重複
數據邊界流模式(無邊界,需應用層協議分幀)數據報模式(保留邊界,<64KB)
開銷較大(連接建立/維護、確認重傳)極小
適用場景文件傳輸、遠程登錄、HTTP、數據庫連接視頻會議、直播、DNS、即時遊戲、廣播

Socket 編程模型:C/S 架構

Socket = IP 地址 + 端口號,雙向通信端點。Java java.net 包提供 Socket(客戶端)和 ServerSocket(服務端)。

通信流程

  1. 服務端:ServerSocket.bind() → listen() → accept() 阻塞等待
  2. 客戶端:new Socket(host, port) 發起連接
  3. 連接建立:雙方獲取 InputStream/OutputStream 進行讀寫
  4. 關閉:雙向關閉流與 Socket

基礎 Socket 客戶端/服務端實現

客戶端

public class TcpClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("127.0.0.1", 8888);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
             BufferedReader console = new BufferedReader(new InputStreamReader(System.in))) {
            
            String input;
            while ((input = console.readLine()) != null) {
                if ("bye".equalsIgnoreCase(input)) break;
                out.println(input);                    // 發送
                String response = in.readLine();       // 接收
                System.out.println("Server: " + response);
            }
        }
    }
}

服務端(單線程,一次只能服務一個客戶端)

public class TcpServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(8888)) {
            System.out.println("Server started on port 8888");
            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)) {
                    
                    String msg;
                    while ((msg = in.readLine()) != null) {
                        if ("bye".equalsIgnoreCase(msg)) break;
                        System.out.println("Client: " + msg);
                        out.println("Echo: " + msg); // 回聲響應
                    }
                } catch (IOException e) {
                    System.err.println("Client error: " + e.getMessage());
                }
            }
        }
    }
}

多線程服務端:支援並發客戶端

主線程 accept() 循環,每個客戶端連接啟動獨立線程處理:

public class ConcurrentTcpServer {
    private static final ExecutorService POOL = Executors.newCachedThreadPool();
    
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(8888)) {
            System.out.println("Concurrent server started on 8888");
            while (true) {
                Socket client = server.accept();
                POOL.execute(new ClientHandler(client));
            }
        } finally {
            POOL.shutdown();
        }
    }
    
    static class ClientHandler implements Runnable {
        private final Socket socket;
        ClientHandler(Socket socket) { this.socket = socket; }
        
        @Override
        public void run() {
            String clientInfo = socket.getRemoteSocketAddress().toString();
            System.out.println("New client: " + clientInfo);
            try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                 PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
                
                String msg;
                while ((msg = in.readLine()) != null) {
                    if ("bye".equalsIgnoreCase(msg)) break;
                    System.out.println(clientInfo + ": " + msg);
                    out.println("Server received: " + msg);
                }
            } catch (IOException e) {
                System.err.println(clientInfo + " error: " + e.getMessage());
            } finally {
                System.out.println("Client disconnected: " + clientInfo);
            }
        }
    }
}

關鍵技術要點與避坑指南

1. 字符編碼統一

顯式指定 StandardCharsets.UTF_8,避免跨平台亂碼。

2. 消息邊界處理(TCP 粘包/拆包)

  • 定長消息:固定字節數讀取
  • 分隔符:換行符 \n、\r\n 或自定義分隔符
  • 長度前綴:4 字節長度 + 消息體(推薦高性能場景)
  • 序列化協議:Protobuf、JSON + 長度/分隔符

3. 資源管理

  • 使用 try-with-resources 自動關閉流與 Socket
  • 異常時確保釋放資源,防止文件描述符洩漏

4. 線程池優化

  • 避免無限創建線程:使用 ThreadPoolExecutor 自定義核心/最大線程、隊列、拒絕策略
  • 考慮 NIO/Netty 替代 BIO 多線程,支持萬級連接

5. 端口選擇

避開 0-1023 系統保留端口(HTTP 80、SSH 22、FTP 21/20 等),選擇 1024-65535 範圍。

UDP 編程簡例

// UDP 發送端
try (DatagramSocket socket = new DatagramSocket()) {
    byte[] data = "Hello UDP".getBytes(StandardCharsets.UTF_8);
    DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 9999);
    socket.send(packet);
}

// UDP 接收端
try (DatagramSocket socket = new DatagramSocket(9999)) {
    byte[] buf = new byte[1024];
    DatagramPacket packet = new DatagramPacket(buf, buf.length);
    socket.receive(packet);
    System.out.println(new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8));
}

進階方向:NIO 與 Netty

  • Java NIO:Selector + ServerSocketChannel + SocketChannel,單線程多路複用
  • Netty:業界標準高性能網絡框架,事件驅動、零拷貝、內存池、協議編解碼器
  • 生產環境高併發長連接場景強烈推薦 Netty

總結

Socket 編程是網絡通信基石。掌握 TCP/UDP 差異、BIO 多線程模型、消息幀解碼、資源管理、線程池優化 可構建穩健網絡應用。高併發場景請轉向 NIO/Netty,享受事件驅動非阻塞架構紅利。

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