設置 Java Socket 選項
1. 前言
前面章節介紹了 Java TCP、UDP Socket 編程方法,按照文中介紹的方法去編寫 Socket 程序,是完全可以正常工作的。其實,TCP/IP 協議棧允許你對 Socket 做一些定制,比如設置 Socket 的接收、發送緩沖區的大小,這就是常說的 Socket 選項。
本文首先會以 Linux 系統為例,介紹操作系統 Socket 選項的基本概念,然后再介紹 Java 中如何去設置 Socket 選項。
2. Socket 選項的概念
操作系統協議棧支持的 Socket 選項參數有很多,匯總起來如下圖所示:
從圖中可以看出,Socket 選項按照級別進行分類,級別有很多種,但是總結起來分兩類:
- 通用 Socket 級別的選項。枚舉值為 SOL_SOCKET。
- 協議相關的選項。協議棧為我們提供了控制所有協議的選項,比如 IP、IPv6、TCP、UDP、ICMP 等。枚舉值的格式為 IPPROTO_XXX,XXX 代表協議。
每一種選項級別下面包含了很多選項參數。比如,通用 Socket 選項的級別枚舉值是 SOL_SOCKET,其下面包含 SO_RCVBUF 和 SO_SNDBUF 選項參數;IP 協議選項的級別的枚舉值是 IPPROTO_IP,其下面包含 IP_TTL、IP_TOS 等選項參數。
在 Linux 系統下,所有的選項參數都可以在幫助手冊里面查找,具體方法如下:
通用 Socket 級別選項參數
man 7 socket
IP 協議級別選項參數:
man 7 ip
IPv6 協議級別選項參數:
man 7 ipv6
TCP 協議級別選項參數:
man 7 tcp
UDP 協議級別選項參數:
man 7 udp
Socket 選項參數最終是如何設置到協議棧的呢?協議棧提供了 getsockopt() 和 setsockopt() 兩個 C 語言函數,分別用于獲取和設置選項參數。
調用兩個函數所需要包含的頭文件,以及他們的聲明如下:
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
如果你對系統本身的 Socket 選項感興趣,可以通過 man 查找相關幫助。本節重點介紹通用 Socket 選項。
3. 通用 Socket 選項
通用 Socket 選項的 level 枚舉值是 SOL_SOCKET。
表格中選項名稱不用多說,數據類型列表示選項值的類型,大多數是整形,還有一些是結構體類型。有的選項是既可以設置值也可以讀取值,用 set 表示;有的選項只能讀取值,用 get 表示。常見選項參數如下:
選項名稱 | 數據類型 | get 或 set | 說明 |
---|---|---|---|
SO_BROADCAST | int | set | 設置 Socket 可以進行局域網廣播,目標 IP 需要填網段的廣播地址或者是統一受限廣播地址 255.255.255.255。 |
SO_KEEPALIVE | int | set | 用于設置 TCP 連接的?;睿话愫苌儆?。 |
SO_LINGER | struct linger | set | 用于設置當 TCP 連接已經關閉,但是未發送數據等待時間。通常設置 SO_LINGER 等待時間為 0,解決大量 TIME_WAIT 狀態的問題。 |
SO_OOBINLINE | int | set | 用于設置將“帶外數據”作為普通數據流來處理。 |
SO_RCVBUF | int | set | 設置 Socket 接收緩沖區大小。 |
SO_REUSEADDR | int | set | 用于設置在調用 bind() 函數時,重用已經 bind 的 Socket 地址。 |
SO_SNDBUF | int | set | 設置 Socket 發送緩沖區大小。 |
4. 常用選項說明
下來,我們對 Socket 編程中常用的 Socket 選項重點介紹。
4.1 SO_REUSEADDR
TCP 連接關閉過程中,主動關閉的一方會處于 TIME_WAIT 狀態,要等待 2MSL 時間。而服務器在工作過程中有可能由于配置的改變而要重啟,或者是由于程序異常奔潰要重新啟動。在這種情況下,如果服務器監聽的 Socket 處于 TIME_WAIT 狀態,那么調用 bind 方法綁定 Socket 就會失敗。如果要等待 2MSL 時間,對于服務器來說是難以接受的。要想解決此問題,需要給監聽 Socket 設置 SO_REUSEADDR 選項。
Java 的 java.net.ServerSocket 類提供了 setReuseAddress 方法,可以用以設置 SO_REUSEADDR 選項,如下:
ServerSocket ss = null;
try {
ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress(8022));
ss.accept();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 注意:
- SO_REUSEADDR 選項需要在 bind 方法調用前設置,所以要創建一個未綁定的 ServerSocket 對象,然后手動執行 bind 操作。
4.2 SO_KEEPALIVE
SO_KEEPALIVE 是協議棧提供的一種連接?;顧C制,一般是用在 TCP 協議中。主要目的是當通信雙方長時間沒有數據交互,然而 Socket還沒有被關閉,協議棧會向對方發送一個 Heartbeat 消息期望對方回復一個 ACK,如果對方能回復說明連接是正常的,如果對方不能回復,嘗試幾次以后就會關閉連接。系統?;畹臅r間一般是 2 小時。
Java 的 java.net.Socket 類提供了 setKeepAlive 方法,可以用以設置 SO_KEEPALIVE 選項,如下:
sock.setKeepAlive(true);
4.3 SO_LINGER
SO_LINGER 是用來設置“連接關閉以后,未發送完的數據包還可以在協議棧逗留的時間”。java.net.Socket 提供了 setSoLinger 方法可以設置 SO_LINGER 選項。原型如下:
public void setSoLinger(boolean on, int linger) throws SocketException
-
如果設置 on 為 false,則該選項的值被忽略,協議棧會采用默認行為。close 調用會立即返回給調用者,協議棧會盡可能將 Socket 發送緩沖區未發送的數據發送完成。
-
如果設置 on 為 true,但是 linger 為 0,當你調用了 close() 方法以后,協議棧將丟棄保留在 Socket 發送緩沖區中未發送完的數據,然后向對方發送一個 RST。這樣連接很快會被關閉,不會進入 TIME_WAIT 狀態,這也是一個避免“由于大量 TIME_WAIT 狀態的 Socket 導致連接失敗“的解決辦法。
-
如果設置 on 為 true ,但是 linger 的取值大于 0,當你調用了 close() 方法以后,如果 Socket 發送緩沖區還有未發送完的數據,那么系統會等待一個指定的時間,close() 才返回。注意,這種情況下 close() 方法返回,并不能保證 Socket 發送緩沖區中未發送的數據被成功發送完。
- 注意:
- 參數 linger 的單位是秒。
sock.setSoLinger(true, 20);
4.4 SO_RCVBUF
SO_RCVBUF 很好理解,用于設置 Socket 的接收緩沖區大小。TCP 一般不需要設置,UDP 可能需要設置。java.net.Socket 類提供了 setReceiveBufferSize 方法可以設置接收緩沖區的大小。
sock.setReceiveBufferSize(16384);
4.5 SO_SNDBUF
SO_SNDBUF 也很好理解,用于設置 Socket 的發送緩沖區大小。一般不需要設置,采用系統默認大小即可。java.net.Socket 類提供了 setSendBufferSize 方法可以設置發送緩沖區的大小。
sock.setSendBufferSize(16384);
4.6 SO_OOBINLINE
SO_OOBINLINE 用于設置將“帶外數據”作為普通數據流來處理。java.net.Socket 類提供了 setOOBInline 方法可以設置 SO_OOBINLINE 選項。
sock.setOOBInline(true);
4.7 TCP_NODELAY
TCP_NODELAY 用于關閉 Nagle 算法,一般是用在實時性要求比較高的場景。java.net.Socket 提供了 setTcpNoDelay 方法用于設置 TCP_NODELAY 選項。
sock.setTcpNoDelay(true);
5 小結
本節重點是介紹在 java 中設置常用 Socket 選項的方法。當然,我們是從 Linux 系統本身提供的 Socket 選項開始的,我們也介紹了在 linux 系統中如何查找 Socket 選項的方法。了解操作系統對 Socket 選項的支持,可以讓你形成一個完整的認識。
文中列出了常用 Socket 選項的應用場景。SO_REUSEADDR 是服務器必須要設置的一個選項,也只有服務器才需要此功能。TCP_NODELAY 是在開發實時性要求很高的程序時,必須要設置的,比如音視頻通信系統。
SO_LINGER 是在服務器端解決“由于 TIME_WAIT 過多,導致連接失敗的問題”時的一個常用方法。其他選項,可以根據需要選擇是否開啟。