在Java的Socket中,主要包含了以下可設定的TCP參數。
屬性
說明
預設值
SO_TIMEOUT
對ServerSocket來說表示等待連接配接的最長空等待時間; 對Socket來說表示讀資料最長空等待時間。
TCP_NODELAY
是否一有資料就馬上發送。
false
SO_LINGER
優雅地關閉套接字,或者立刻關閉。
-1
SO_SNDBUF
發送資料的緩沖區大小。
8K
SO_RCVBUF
接收資料的緩沖區大小。
SO_KEEPALIVE
是否啟用心跳機制。
SO_REUSEADDR
是否位址重用。
BACKLOG
服務端處理線程全忙後,允許多少個新請求進入等待。
50
BACKLOG用于構造服務端套接字ServerSocket對象,辨別當伺服器請求處理線程全滿時,用于臨時存放已完成三次握手的請求的隊列的最大長度。如果未設定或所設定的值小于1,Java将使用預設值50。
ServerSocket serverSocket = new ServerSocket(8080, 100);
在TCP/IP協定中,無論發送多少資料,總是要在資料前面加上協定頭,同時,對方接收到資料,也需要發送ACK表示确認。為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的資料。這裡就涉及到一個名為Nagle的算法,該算法的目的就是為了盡可能發送大塊資料,避免網絡中充斥着許多小資料塊。
TCP_NODELAY選項,就是用于啟用或關于Nagle算法。如果要求高實時性,有資料發送時就馬上發送,就将該選項設定為true關閉Nagle算法;如果要減少發送次數減少網絡互動,就設定為false等累積一定大小後再發送。預設為false。
Socket中操作該屬性的方法如下:
void setTcpNoDelay(boolean on)
啟用/禁用 TCP_NODELAY(啟用/禁用 Nagle 算法)。
boolean getTcpNoDelay()
測試是否啟用 TCP_NODELAY。
關于Nagle算法介紹,請參考附錄部分。
對于服務端套接字ServerSocket來說,SO_TIMEOUT表示服務端accept方法空等待用戶端連接配接的最長時間;對于用戶端套接字Socket來說,SO_TIMEOUT表示輸入流讀取資料read方法的最長等待時間。一旦超過設定的SO_TIMEOUT,程度将抛出逾時異常。
ServerSocket/Socket中操作該屬性的方法如下:
int getSoTimeout()
傳回 SO_TIMEOUT 的設定。
void setSoTimeout(int timeout)
啟用/禁用帶有指定逾時值的 SO_TIMEOUT,以毫秒為機關。
使用示例:
ServerSocket serverSocket = new ServerSocket(8080);
serverSocket.setSoTimeout(30000);
Socket clientSocket = serverSocket.accept();
clientSocket.setSoTimeout(20000);
當調用closesocket關閉套接字時,SO_LINGER将決定系統如何處理殘存在套接字發送隊列中的資料。處理方式無非兩種:丢棄或者将資料繼續發送至對端,優雅關閉連接配接。事實上,SO_LINGER并不被推薦使用,大多數情況下我們推薦使用預設的關閉方式(即下方表格中的第一種情況)。
下方代碼段顯示linger結構文法,表格為不同參數情況下的套接字行為。
typedef struct linger {
u_short l_onoff; //開關,零或者非零
u_short l_linger; //優雅關閉最長時限
} linger;
各字段與對應行為如下表所示。
l_onoff
l_linger
closesocket行為
發送隊列
底層行為
零
忽略
立即傳回。
保持直至發送完成。
系統接管套接字并保證将資料發送至對端。
非零
立即放棄。
直接發送RST包,自身立即複位,不用經過2MSL狀态。對端收到複位錯誤号。
阻塞直到l_linger時間逾時或資料發送完成。(套接字必須設定為阻塞)
在逾時時間段内保持嘗試發送,若逾時則立即放棄。
逾時則同第二種情況,若發送完成則皆大歡喜。
void setSoLinger(boolean on, int linger)
啟用/禁用具有指定逗留時間(以秒為機關)的SO_LINGER。 Linger最大取值為65535。
int getSoLinger()
傳回 SO_LINGER 的設定。預設值為-1。
由于getSoLinger()方法傳回的-1沒有太多意思,我們檢視到Java的預設實作PlainSocketImpl.c檔案中,指派操作代碼片段如下所示。
/*
* Class: java_net_PlainSocketImpl
* Method: socketSetOption
* Signature: (IZLjava/lang/Object;)V
*/
JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this,
jint cmd, jboolean on,
jobject value) {
…
switch (cmd) {
case java_net_SocketOptions_SO_SNDBUF :
case java_net_SocketOptions_SO_RCVBUF :
case java_net_SocketOptions_SO_LINGER :
case java_net_SocketOptions_IP_TOS :
{
…
if (cmd == java_net_SocketOptions_SO_LINGER) {
if (on) {
optval.ling.l_onoff = 1;
optval.ling.l_linger = (*env)->GetIntField(env, value, fid);
} else {
optval.ling.l_onoff = 0;
optval.ling.l_linger = 0;
}
optlen = sizeof(optval.ling);
} else {
optval.i = (*env)->GetIntField(env, value, fid);
optlen = sizeof(optval.i);
}
break;
}
/* Boolean -> int */
default :
optval.i = (on ? 1 : 0);
optlen = sizeof(optval.i);
}
…
}
從藍色字型部分代碼可以看出,隻要指派為false,則底層linger結構中的l_onoff和l_linger的值均為0,符合表中的第一種情況。
發送緩沖區的大小設定,預設為8K。
void setSendBufferSize(int size)
将此 Socket 的 SO_SNDBUF 選項設定為指定的值。
int getSendBufferSize()
擷取此 Socket 的 SO_SNDBUF 選項的值,該值是平台在 Socket 上輸出時使用的緩沖區大小。
接收緩沖區大小設定,預設為8K。該屬性既可以在ServerSocket執行個體中設定,也可以在Socket執行個體中設定。
void setReceiveBufferSize(int size)
将此 Socket 的 SO_RCVBUF 選項設定為指定的值。
int getReceiveBufferSize()
擷取此 Socket 的 SO_RCVBUF 選項的值,該值是平台在 Socket 上輸入時使用的緩沖區大小。
套接字本身是有一套心跳保活機制的,不過預設的設定并不像我們一廂情願的那樣有效。在雙方TCP套接字建立連接配接後(即都進入ESTABLISHED狀态)并且在兩個小時左右上層沒有任何資料傳輸的情況下,這套機制才會被激活。
很多人認為兩個小時的時間設定得很不合理。為什麼不設定成為10分鐘,或者更短的時間?(可以通過SO_KEEPALIVE選項設定。)但是這樣做其實并不被推薦。實際上這套機制隻是作業系統底層使用的一個被動機制,原理上不應該被上層應用層使用。當系統關閉一個由KEEPALIVE機制檢查出來的死連接配接時,是不會主動通知上層應用的,隻有在調用相應的IO操作在傳回值中檢查出來。
在《UNIX網絡程式設計第1卷》中也有詳細的闡述:
SO_KEEPALIVE 保持連接配接檢測對方主機是否崩潰,避免(伺服器)永遠阻塞于TCP連接配接的輸入。設定該選項後,如果2小時内在此套接口的任一方向都沒有資料交換,TCP就自動給對方 發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會導緻以下三種情況:對方接收一切正常:以期望的ACK響應。2小時後,TCP将發出另一個探測分節。對方已崩潰且已重新啟動:以RST響應。套接口的待處理錯誤被置為ECONNRESET,套接口本身則被關閉。對方無任何響應:源自berkeley的TCP發送另外8個探測分節,相隔75秒一個,試圖得到一個響應。在發出第一個探測分節11分鐘 15秒後若仍無響應就放棄。套接口的待處理錯誤被置為ETIMEOUT,套接口本身則被關閉。如ICMP錯誤是“host unreachable(主機不可達)”,說明對方主機并沒有崩潰,但是不可達,這種情況下待處理錯誤被置為 EHOSTUNREACH。
是以,忘記SO_KEEPALIVE,在應用層自己寫一套保活機制比較靠譜。
boolean getKeepAlive()
測試是否啟用 SO_KEEPALIVE。
void setKeepAlive(boolean on)
啟用/禁用 SO_KEEPALIVE。
是否重用處于TIME_WAIT狀态的位址。預設為false。
boolean getReuseAddress()
測試是否啟用 SO_REUSEADDR。
void setReuseAddress(boolean on)
啟用/禁用 SO_REUSEADDR 套接字選項。
QQ:519841366
本頁版權歸作者和部落格園所有,歡迎轉載,但未經作者同意必須保留此段聲明,
且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利