天天看點

Java套接字Socket程式設計--TCP參數

在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

本頁版權歸作者和部落格園所有,歡迎轉載,但未經作者同意必須保留此段聲明,

且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利