Java java.net 包提供了三層網絡抽象,由高到低:
原則:**能用高層 API 解決的,絕不手寫底層協議解析。** 本文以 URLConnection 為切入點,層層深入至 Socket 核心模型。
URL url = new URL("https://api.example.com/users/123");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 1. 配置請求 (連接前必須設置)
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + token);
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
conn.setInstanceFollowRedirects(true);
// 2. 發送請求 (getInputStream 隱式觸發連接)
try (InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line).append('\n');
String response = sb.toString();
// JSON 解析...
}
// 3. 錯誤處理 (非 2xx 狀態碼拋 IOException,需捕獲後讀取 errorStream)
catch (IOException e) {
try (InputStream es = conn.getErrorStream()) { /* 讀取錯誤詳情 */ }
throw e;
} finally {
conn.disconnect(); // 釋放連接,歸還連接池 (若啟用)
}
setInstanceFollowRedirects(true) (默認開啟),支持 301/302/307/308。setUseCaches(true) + ResponseCache.setDefault() 實現 HTTP 緩存 (ETag/Last-Modified)。setDoOutput(true) + setFixedLengthStreamingMode(len) / setChunkedStreamingMode(len) 避免大文件 OOM。multipart/form-data 體,或引入 Apache HttpClient/OkHttp 簡化。// 客戶端
Socket socket = new Socket();
socket.connect(new InetSocketAddress("host", 8080), 3000); // 連接超時
socket.setSoTimeout(5000); // 讀超時
socket.setTcpNoDelay(true); // 關閉 Nagle 算法,低延遲場景關鍵
socket.setKeepAlive(true); // 開啟 TCP 心跳 (約 2 小時默認間隔)
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
// 發送請求 (自定義協議:JSON + 換行符分幀)
bw.write("{\"cmd\":\"ping\"}\n");
bw.flush();
// 讀取響應 (阻塞直到換行符或超時)
String response = br.readLine();
// 解析處理...
}
// 服務端
ServerSocket server = new ServerSocket(8080);
server.setReuseAddress(true); // 避免 TIME_WAIT 導致重啟失敗
while (true) {
Socket client = server.accept(); // 阻塞等待連接
// 推薦:提交線程池異步處理,或使用 NIO Selector 多路復用
executor.submit(() -> handleClient(client));
}
| 選項 | 方法 | 建議值 | 場景 |
|---|---|---|---|
| TCP_NODELAY | setTcpNoDelay(true) | true | 即時通訊、RPC、游戲、金融交易 (禁用 Nagle 合包) |
| SO_KEEPALIVE | setKeepAlive(true) | true | 長連接存活檢測,防「僵屍連接」 (依賴內核默認 2h,可調 tcp_keepalive_time) |
| SO_RCVBUF / SO_SNDBUF | setReceiveBufferSize / setSendBufferSize | 64KB-256KB | 高吞吐/大數據傳輸,建議配合 BufferedInputStream 而非盲目增大 |
| SO_LINGER | setSoLinger(true, seconds) | 0 或 短時間 | 0=立即發 RST 關閉 (拋棄發送緩衝區);>0=優雅關閉等待 |
| SO_TIMEOUT | setSoTimeout(ms) | 3000-10000 | 讀操作超時,**必須設置**,防止線程無限阻塞 |
| 模式 | 線程模型 | 並發能力 | 複雜度 | 典型框架 |
|---|---|---|---|---|
| 阻塞多線程 (BIO) | 每連接一線程 | ~1000 (線程開銷大) | 低 | 早期 Tomcat, 簡單 RPC |
| NIO 多路復用 (Reactor) | 單/少量線程 + Selector | ~10K-100K+ | 高 (手寫極難) | Netty, Mina, Jetty, Tomcat NIO |
| AIO (Proactor) | 內核完成通知 | 理論更高 | 極高 (Windows IOCP 強,Linux 支持弱) | 較少直接使用 |
// 1. 打開通道與選擇器
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
Selector selector = Selector.open();
// 2. 註冊 Accept 事件
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 3. 事件循環
while (selector.select() > 0) {
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ); // 註冊讀事件
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int n = client.read(buf);
if (n > 0) { /* 處理數據 */ }
else if (n == -1) { client.close(); } // 對端關閉
}
// 處理 OP_WRITE (發送緩衝區可寫) ...
}
selector.selectedKeys().clear();
}
DatagramChannel (NIO) + Netty,或用戶態網絠棧 (DPDK/XDP) 極致場景。SO_REUSEADDR、調整 tcp_tw_reuse/tcp_tw_recycle (內核參數)、優先用長連接/連接池。tcpdump -i any -nn -S port 8080 抓包分析;ss -tan state established 連接狀態;jstack 定位線程阻塞於哪個 read()。StandardCharsets.UTF_8,勿依賴平台默認編碼。從 URLConnection 的聲明式便捷,到 Socket 的字節流掌控,再到 NIO Selector 的事件驅動,Java 網絡 API 為不同複雜度場景提供了分層工具箱。現代後端開發,**90% 場景應直接拥抱成熟框架 (Netty, HttpClient, gRPC)**,將精力聚焦於業務協議設計、服務治理、可觀測性建設。理解底層原理,是為了在框架失效、性能調優、疑難排查時,能穿透抽象層直搗黃龍。網絡編程的盡頭,不是寫更多 Socket 代碼,而是**寫更少、更穩、更可觀測的網絡代碼**。