Socket 核心概念
- Socket (套接字):IP 位址 + 埠號的封裝,網路通訊端點
- 雙向通訊:雙方各有 Socket,透過 I/O 流/資料包交換資料
- C/S 模型:Server 監聽埠,Client 主動連線,一 Server 可服務多 Client
TCP Socket 程式設計 (可靠、有序、面向連線)
伺服端步驟
ServerSocket server = new ServerSocket(port) 綁定監聽
Socket client = server.accept() 阻塞等待連線,回傳新 Socket
- 取得串流:
client.getInputStream() / client.getOutputStream()
- 讀寫資料 (建議包裝
BufferedReader/PrintWriter)
- 關閉資源 (finally 區塊或 try-with-resources)
客戶端步驟
Socket socket = new Socket(host, port) 主動連線
- 取得串流,讀寫資料
- 關閉資源
完整實戰範例:大寫轉換伺服器
// TCPServer.java
public class TCPServer {
public static void main(String[] args) throws IOException {
try (ServerSocket server = new ServerSocket(8888)) {
System.out.println("Server 啟動,監聽 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 clientIp = client.getInetAddress().getHostAddress();
System.out.println(clientIp + " 連線成功");
String line;
while ((line = in.readLine()) != null) {
if ("over".equalsIgnoreCase(line)) break;
System.out.println("收到: " + line);
out.println(line.toUpperCase()); // 回傳大寫
}
System.out.println(clientIp + " 斷開連線");
} catch (IOException e) {
System.err.println("客戶端處理異常: " + e.getMessage());
}
}
}
}
}
// TCPClient.java
public class TCPClient {
public static void main(String[] args) throws IOException {
try (Socket socket = new Socket("127.0.0.1", 8888);
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
System.out.println("已連線至伺服器,輸入 'over' 結束");
String line;
while ((line = keyboard.readLine()) != null) {
out.println(line);
if ("over".equalsIgnoreCase(line)) break;
System.out.println("伺服器回應: " + in.readLine());
}
}
}
}
編譯執行指令
# 終端機 1: 啟動伺服器
javac TCPServer.java && java TCPServer
# 終端機 2: 啟動客戶端
javac TCPClient.java && java TCPClient
UDP Socket 程式設計 (不可靠、無序、無連線、低延遲)
核心類別
DatagramSocket:發送/接收端點,綁定本地埠
DatagramPacket:資料包封裝 (資料緩衝區、長度、目標 IP/埠、來源 IP/埠)
發送端步驟
DatagramSocket socket = new DatagramSocket() (系統自動分配本地埠)
- 建立封包:
new DatagramPacket(data, length, InetAddress.getByName(host), port)
socket.send(packet)
- 關閉 Socket
接收端步驟
DatagramSocket socket = new DatagramSocket(port) 綁定監聽埠
- 準備緩衝區:
byte[] buf = new byte[1024]
- 建立空封包:
DatagramPacket packet = new DatagramPacket(buf, buf.length)
socket.receive(packet) 阻塞接收
- 解析:
packet.getData()、packet.getLength()、packet.getAddress()、packet.getPort()
- 關閉 Socket
完整實戰範例:雙向回應
// UDPSender.java
public class UDPSender {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket()) {
String msg = "I love you";
byte[] data = msg.getBytes(StandardCharsets.UTF_8);
// 發送
DatagramPacket sendPacket = new DatagramPacket(data, data.length, InetAddress.getByName("localhost"), 6666);
socket.send(sendPacket);
System.out.println("已發送: " + msg);
// 接收回應
byte[] buf = new byte[1024];
DatagramPacket recvPacket = new DatagramPacket(buf, buf.length);
socket.receive(recvPacket);
String reply = new String(recvPacket.getData(), 0, recvPacket.getLength(), StandardCharsets.UTF_8);
System.out.println("收到回應: " + reply);
}
}
}
// UDPReceiver.java
public class UDPReceiver {
public static void main(String[] args) throws IOException {
try (DatagramSocket socket = new DatagramSocket(6666)) {
System.out.println("Receiver 啟動,監聽 6666...");
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // 阻塞等待
String received = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
System.out.println("收到: " + received + " from " + packet.getAddress() + ":" + packet.getPort());
// 回應
String reply = "Me too";
byte[] replyData = reply.getBytes(StandardCharsets.UTF_8);
DatagramPacket replyPacket = new DatagramPacket(replyData, replyData.length, packet.getAddress(), packet.getPort());
socket.send(replyPacket);
System.out.println("已回應: " + reply);
}
}
}
編譯執行 (先啟動接收端)
# 終端機 1
javac UDPReceiver.java && java UDPReceiver
# 終端機 2
javac UDPSender.java && java UDPSender
TCP vs UDP 選型指南
| 維度 | TCP | UDP |
| 可靠性 | 保證送達、重傳、排序 | 不保證,可能丟包/亂序/重複 |
| 連線建立 | 三次交握 (延遲) | 無連線,即發即走 |
| 流量控制 | 滑動視窗、阻塞控制 | 無,應用層自行控制 |
| 資料單位 | 位元組流 (無邊界) | 資料包 (保留邊界) |
| 適用場景 | HTTP、DB、郵件、檔案傳輸、即時通訊核心 | 直播、VoIP、遊戲同步、DNS、SNMP、廣播 |
| Java 類別 | Socket / ServerSocket | DatagramSocket / DatagramPacket |
進階提示
- TCP 粘包/拆包:串流無邊界,應用層需自訂協定 (長度前綴、分隔符、固定長度)
- NIO / Netty:高併發場景請使用非阻塞 I/O (NIO) 或 Netty 框架,避免一執行緒一連線模型瓶頸
- UDP 可靠性補強:應用層實作 ACK/重傳/序號 (如 QUIC、KCP、RUDP)
- 資源管理:務必使用 try-with-resources 確保 Socket 關閉,避免檔案描述符洩漏