接續上一篇,本文深入講解 Socket 類中用於獲取與設置 Socket 選項的 8 對 getter/setter 方法。這些選項定義於 java.net.SocketOptions 介面,直接影響數據傳輸行為、性能與可靠性。
| 常量名 | 值 | 對應方法 | 用途 |
|---|---|---|---|
| TCP_NODELAY | 0x0001 | getTcpNoDelay / setTcpNoDelay | 控制 Nagle 算法開關 |
| SO_REUSEADDR | 0x0004 | getReuseAddress / setReuseAddress | 允許端口重用 |
| SO_LINGER | 0x0080 | getSoLinger / setSoLinger | 控制 close() 延遲關閉行為 |
| SO_TIMEOUT | 0x1006 | getSoTimeout / setSoTimeout | 設置讀取超時(毫秒) |
| SO_SNDBUF | 0x1001 | getSendBufferSize / setSendBufferSize | 發送緩衝區大小 |
| SO_RCVBUF | 0x1002 | getReceiveBufferSize / setReceiveBufferSize | 接收緩衝區大小 |
| SO_KEEPALIVE | 0x0008 | getKeepAlive / setKeepAlive | 啟用心跳保活機制 |
| SO_OOBINLINE | 0x1003 | getOOBInline / setOOBInline | 帶外數據內聯接收 |
預設開啟。Nagle 算法會將小數據包合併發送,減少網路擁塞,但會引入延遲。對於即時性要求高的場景(遊戲、Telnet、金融交易),需關閉:
socket.setTcpNoDelay(true); // 禁用 Nagle,每次 write 立即發送
獲取狀態:boolean on = socket.getTcpNoDelay();
允許多個 Socket 綁定同一端口,解決 Address already in use: JVM_Bind 問題。必須在 bind() 前調用,且所有共用端口的 Socket 皆需啟用:
Socket s1 = new Socket();
s1.setReuseAddress(true);
s1.bind(new InetSocketAddress("127.0.0.1", 88));
Socket s2 = new Socket();
s2.setReuseAddress(true);
s2.bind(new InetSocketAddress("127.0.0.1", 88)); // 成功
注意:構造方法綁定端口時無法設置此選項,必須用無參構造 + bind()。
影響 close() 行為:
setSoLinger(true, n):關閉時最多阻塞 n 秒(0~65535),嘗試發送未完成數據;超時則丟棄數據並返回。setSoLinger(false, 0) 或 setSoLinger(true, 0):立即返回,未發送數據丟棄(預設行為)。if (socket.getSoLinger() == -1) { // -1 表示未啟用
socket.setSoLinger(true, 60); // 設置 1 分鐘延遲關閉
}
底層不支持時拋 SocketException;linger 為負數拋 IllegalArgumentException。
setSoTimeout(ms) 設置 read() 最大阻塞時間。0 表示無限等待(預設)。超時拋 InterruptedIOException(即 SocketTimeoutException),流保持開啟可重試:
socket.setSoTimeout(30_000); // 30 秒讀取超時
負數拋 IllegalArgumentException;底層不支持拋 SocketException。
預設 8KB。過小會導致頻繁系統調用降低吞吐;過大可能造成內存壓力。建議根據網路帶寬延遲積(BDP)調整:
socket.setSendBufferSize(64 * 1024); // 64KB 發送緩衝
socket.setReceiveBufferSize(64 * 1024); // 64KB 接收緩衝
size 必須為正整數,否則拋 IllegalArgumentException。
啟用後,空閒約 2 小時發送探測包;若 11 分鐘無響應再發一次;12 分鐘仍無響應則關閉連接。適用於長連接檢測對端存活:
socket.setKeepAlive(true); // 啟用保活
啟用後,可用 sendUrgentData(int) 發送單字節緊急數據(繞過發送緩衝區立即發送)。接收端需同樣啟用此選項,否則無法接收。接收時帶外數據與普通數據混合在輸入流中,無法區分來源:
// 發送端
socket.setOOBInline(true);
socket.sendUrgentData(65); // 發送 'A' (低字節)
// 接收端
serverSocket.setOOBInline(true);
InputStream in = socket.getInputStream();
// 讀取時 'A' 會夾雜在普通數據流中
sendUrgentData 僅發送 int 低 8 位;高 24 位被忽略。
// Server.java
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(1234);
System.out.println("Server started on 1234");
while (true) {
Socket s = server.accept();
s.setOOBInline(true);
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println(br.readLine()); // 讀取兩行
System.out.println(br.readLine());
s.close();
}
}
}
// Client.java
public class Client {
public static void main(String[] args) throws Exception {
Socket s = new Socket("127.0.0.1", 1234);
s.setOOBInline(true);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write('C'); bw.write("hello world\r\n"); bw.flush();
s.sendUrgentData(65); // 'A'
s.sendUrgentData(322); // 低字節 66 -> 'B'
bw.flush();
s.sendUrgentData(214); s.sendUrgentData(208); // "中" 兩字節
s.sendUrgentData(185); s.sendUrgentData(250); // "國" 兩字節
s.close();
}
}
運行結果顯示帶外數據優先到達:ABChello world\n中國。驗證了 sendUrgentData 立即發送特性,而普通 write 需 flush()。
SocketException(底層不支持)或 IllegalArgumentException(參數非法)。SO_REUSEADDR、SO_LINGER 必須在 bind()/connect() 前設置。SO_OOBINLINE 需雙端同時啟用。