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

Java 網絡編程核心:TCP/UDP Socket 通信完整實戰與對比

     編輯:Java 相關知識

Java 提供 java.net 包支援 TCP (可靠流式) 和 UDP (不可靠數據報) 兩種傳輸層協議編程。本文通過完整客戶端/服務端代碼實例,深度對比兩種協議的程式設計模型、適用場景與關鍵注意點。

TCP (Transmission Control Protocol) - 面向連接、可靠字節流

核心特徵

  • 三次握手建立連接:SYN → SYN+ACK → ACK
  • 字節流無邊界:應用層需自定義協議分幀 (長度前綴、分隔符、固定長度)
  • 可靠交付:序列號、確認應答、重傳、滑動窗口流控
  • 全雙工通信:雙向獨立 InputStream/OutputStream

服務端編程模型

public class TcpServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(10001)) {
            System.out.println("TCP Server started on port 10001");
            
            while (true) {
                // 阻塞等待客戶端連接
                Socket clientSocket = serverSocket.accept();
                System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
                
                // 每個客戶端啟動獨立線程處理 (生產環境用線程池)
                new Thread(() -> handleClient(clientSocket)).start();
            }
        }
    }
    
    private static void handleClient(Socket socket) {
        try (socket;
             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;
            while ((line = in.readLine()) != null) {
                System.out.println("Received: " + line);
                // 業務處理...
                out.println("Server echo: " + line);
            }
        } catch (IOException e) {
            System.err.println("Client handling error: " + e.getMessage());
        }
    }
}

客戶端編程模型

public class TcpClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("localhost", 10001);
             PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             BufferedReader console = new BufferedReader(new InputStreamReader(System.in))) {
            
            System.out.println("Connected to server. Type messages (empty to quit):");
            String line;
            while ((line = console.readLine()) != null && !line.isEmpty()) {
                out.println(line);
                System.out.println("Server reply: " + in.readLine());
            }
        }
    }
}

TCP 關鍵注意點

問題原因解決
粘包/半包字節流無消息邊界應用層協議:長度前綴(4字節)、分隔符(\n)、固定長度、Netty LengthFieldBasedFrameDecoder
阻塞 I/O 線程浪費每連接一線程NIO (Selector) / Netty / 虛擬線程 (JDK 21+)
連接泄露未正確關閉 Sockettry-with-resources / finally 關閉
服務端端口佔用TIME_WAIT 狀態serverSocket.setReuseAddress(true)

UDP (User Datagram Protocol) - 無連接、不可靠數據報

核心特徵

  • 無連接建立:直接發送 DatagramPacket
  • 保留消息邊界:每次 send/receive 一個完整數據報 (最大 64KB)
  • 不可靠:可能丟失、重複、亂序、無流控
  • 低延遲、開銷小:適合即時性要求高、容忍丟包場景

服務端編程模型

public class UdpServer {
    public static void main(String[] args) throws IOException {
        try (DatagramSocket socket = new DatagramSocket(7778)) {
            System.out.println("UDP Server started on port 7778");
            byte[] buffer = new byte[1024];
            
            while (true) {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet); // 阻塞接收
                
                String msg = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
                InetAddress clientAddr = packet.getAddress();
                int clientPort = packet.getPort();
                System.out.println("From " + clientAddr + ":" + clientPort + " - " + msg);
                
                // 回複 (使用收到包的地址/端口)
                String reply = "Server received: " + msg;
                byte[] replyBytes = reply.getBytes(StandardCharsets.UTF_8);
                DatagramPacket replyPacket = new DatagramPacket(replyBytes, replyBytes.length, clientAddr, clientPort);
                socket.send(replyPacket);
            }
        }
    }
}

客戶端編程模型

public class UdpClient {
    public static void main(String[] args) throws IOException {
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress serverAddr = InetAddress.getByName("localhost");
            int serverPort = 7778;
            
            // 發送
            String msg = "Hello UDP Server";
            byte[] sendBuf = msg.getBytes(StandardCharsets.UTF_8);
            DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, serverAddr, serverPort);
            socket.send(sendPacket);
            System.out.println("Sent: " + msg);
            
            // 接收回複 (設置超時防止永久阻塞)
            socket.setSoTimeout(5000); // 5秒超時
            byte[] recvBuf = new byte[1024];
            DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
            
            try {
                socket.receive(recvPacket);
                String reply = new String(recvPacket.getData(), 0, recvPacket.getLength(), StandardCharsets.UTF_8);
                System.out.println("Reply: " + reply);
            } catch (SocketTimeoutException e) {
                System.err.println("No reply within timeout");
            }
        }
    }
}

UDP 關鍵注意點

問題原因解決
數據報丟失不可靠協議應用層實現 ACK/重傳/序列號 (如 QUIC、RUDP)
接收緩衝區溢出處理速度 < 到達速度增大緩衝區 socket.setReceiveBufferSize()、加快處理
廣播/多播需特殊地址/權限socket.setBroadcast(true)、MulticastSocket
NAT 穿透客戶端在內網STUN/TURN/ICE、打洞技術

TCP vs UDP 全方位對比

維度TCPUDP
連接建立三次握手 (1.5 RTT)無
可靠性保證有序、不重複、不丟失不保證
消息邊界無 (字節流)有 (數據報)
流控/擁塞控制滑動窗口、慢啟動、快速恢復無
首部開銷20-60 字節8 字節
適用場景HTTP、DB、RPC、文件傳輸、郵件視頻會議、直播、遊戲、DNS、IoT、日誌傳輸
Java 類ServerSocket, SocketDatagramSocket, DatagramPacket

進階:NIO 非阻塞 I/O (Java 1.4+) 與 Netty

NIO 核心組件

  • Channel (SocketChannel, ServerSocketChannel, DatagramChannel) - 雙向、可非阻塞
  • Buffer (ByteBuffer) - 讀寫數據容器、位置/限制/容量
  • Selector - 多路複用器,單線程管理成千上萬 Channel

Netty 架構優勢 (生產環境首選)

// Netty TCP Server 簡例
public class NettyTcpServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            new ServerBootstrap()
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override protected void initChannel(SocketChannel ch) {
                        ch.pipeline()
                            .addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)) // 解決粘包
                            .addLast(new StringDecoder(StandardCharsets.UTF_8))
                            .addLast(new SimpleChannelInboundHandler<String>() {
                                @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                                    ctx.writeAndFlush("Echo: " + msg + "\n");
                                }
                            });
                    }
                })
                .bind(10001).sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

總結

TCP 提供可靠字節流,適合數據完整性至關重要的場景,但需自行處理粘包/半包;UDP 提供低延遲數據報,保留邊界但不可靠,適合即時性優先場景。Java 傳統 BIO (Socket/DatagramSocket) 適合學習與低併發,生產環境高併發建議使用 Netty (NIO 封裝) 或 虛擬線程 (JDK 21+)。理解協議本質、選對模型、處理邊界條件,是網絡編程的核心童子功。

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