亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

socket連接關閉問題的示例分析

發布時間:2022-02-08 09:35:04 來源:億速云 閱讀:166 作者:小新 欄目:開發技術

這篇文章主要為大家展示了“socket連接關閉問題的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“socket連接關閉問題的示例分析”這篇文章吧。

socket編程過程中往往會遇到這樣那樣的問題,出現了這些問題,有的是由于并發訪問量太大造成的,有些卻是由于代碼中編程不慎造成的。比如說,最常見的錯誤就是程序中報打開的文件數過多這個錯誤。socket建立連接的時候是三次握手,這個大家都很清楚,但是socket關閉連接的時候,需要進行四次揮手,但很多人對于這四次揮手的具體流程不清楚,吃了很多虧。

CLOSE_WAIT分析

socket是一種全雙工的通信方式,建立完socket連接后,連接的任何一方都可以發起關閉操作。這里不妨假設連接的關閉是客戶端發起。客戶端的代碼如下:

代碼片段1.1

ret = CS_GetConnect(&client,ipAddr,9010);
if (ret == 0) {
    printf("connected success.");
}
CloseSocket(client);

基本邏輯就是,連接建立后立即關閉。其中CloseSocket函數是自定義函數,僅僅封裝了在windows和linux下關閉socket的不同實現而已

代碼片段1.2

#if defined(WIN32) || defined(WIN64)
#define CloseSocket(fd) do{ closesocket(fd);/* shutdown(fd, 2);*/ }while(0)
#else
#define CloseSocket(fd) do{ close(fd); /*shutdown(fd,2);*/ }while(0)
#endif

socket連接關閉問題的示例分析

客戶端調用了CloseSocket之后,發送FIN信號到服務器端,告訴socket程序,連接已經斷開。服務器端接收到FIN信號后,會將自身的TCP狀態置為`CLOSE_WAIT`,同時回復 一個ACK信號給客戶端,客戶端接收到這個ACK信號后,自身將處于`FIN_WAIT_2`狀態。

但是tcp是全雙工的通信協議,雖然客戶端關閉了連接,但是服務器端對于這個關閉動作不予理睬怎么辦。對于服務器端來說,這是個不幸的消息,因為它將一直處于`CLOSE_WAIT`狀態,雖然客戶端已經不需要和服務器間進行通信了,但是服務器端的socket連接句柄一直得不到釋放;如果老是有這種情況出現,久而久之服務器端的連接句柄就會被耗盡。對于發起關閉的客戶端來說,他處于`FIN_WAIT_2`狀態,如果出現服務器端一直處于`CLOSE_WATI`狀態的情況,客戶端并不會一直處在`FIN_WAIT_2`狀態,因為這個狀態有一個超時時間,這個值可以在/etc/sysctl.conf中進行配置。在這個文件中配置`net.ipv4.tcp_fin_timeout=30`即可保證`FIN_WAIT_2`狀態最多保持30秒,超過這個時間后就進入TIME_WAIT狀態(下面要講到這個狀態)。

注意:這里socket的關閉從客戶端發起,僅僅是為了舉例說明,socket的關閉完全也可以從服務器端發起。比如說你寫了一個爬蟲程序去下載互聯網上的某些web服務器上的資源的時候,某些要下載的web資源不存在,web服務器會立即關閉當前的socket連接,但是你的爬蟲程序不夠健壯,對于這種情況沒有做處理,同樣會使你的爬蟲客戶端處于CLOSE_WAIT狀態。

那么怎樣預防SOCKET處于CLOSE_WATI狀態呢,答案在這里:

代碼片段1.3

    while(true) {
        memset(getBuffer,0,MY_SOCKET_BUFFER_SIZE);
        Ret = recv(client, getBuffer, MY_SOCKET_BUFFER_SIZE, 0);
        if ( Ret == 0 || Ret == SOCKET_ERROR ) 
        {
            printf("對方socket已經退出,Ret【%d】!\n",Ret);
            Ret = SOCKET_READE_ERROR;//接收服務器端信息失敗
            break;
        }
    }
clear:
    if (getBuffer != NULL) {
        free(getBuffer);
        getBuffer = NULL;
    }
    closesocket(client);

這里摘錄了服務器端部分代碼,注意這個recv函數,這個函數在連接建立時,會堵塞住當前代碼,等有數據接收成功后才返回,返回值為接收到的字節數;但是對于連接對方socket關閉情況,它能立即感應到,并且返回0.所以對于返回0的時候,可以跳出循環,結束當前socket處理,進行一些垃圾回收工作,注意最后一句closesocket操作是很重要的,假設沒有寫這句話,服務器端會一直處于CLOSE_WAIT狀態。如果寫了這句話,那么socket的流程就會是這樣的:

socket連接關閉問題的示例分析

TIME_WAIT分析

服務器端調用了CloseSocket操作后,會發送一個FIN信號給客戶端,客戶端進入`TIME_WAIT`狀態,而且將維持在這個狀態一段時間,這個時間也被成為2MSL(MSL是maximum segment lifetime的縮寫,意指最大分節生命周期,這是IP數據包能在互聯網上生存的最長時間,超過這個時間將在互聯網上消失),在這個時間段內如果客戶端的發出的數據還沒有被服務器端確認接收的話,可以趁這個時間等待服務端的確認消息。注意,客戶端最后發出的ACK N+1消息,是一進入`TIME_WAIT`狀態后就發出的,并不是在`TIME_WAIT`狀態結束后發出的。如果在發送ACK N+1的時候,由于某種原因服務器端沒有收到,那么服務器端會重新發送FIN N消息,這個時候如果客戶端還處于`TIME_WAIT`狀態的,會重新發送ACK N+1消息,否則客戶端會直接發送一個RST消息,告訴服務器端socket連接已經不存在了。

有時,我們在使用netstat命令查看web服務器端的tcp狀態的時候,會發現有成千上萬的連接句柄處在`TIME_WAIT`狀態。web服務器的socket連接一般都是服務器端主動關閉的,當web服務器的并發訪問量過大的時候,由于web服務器大多情況下是短連接,socket句柄的生命周期比較短,于是乎就出現了大量的句柄堵在`TIME_WAIT`狀態,等待系統回收的情況。如果這種情況太過頻繁,又由于操作系統本身的連接數就有限,勢必會影響正常的socket連接的建立。在linux下對于這種情況倒是有解救措施,方法就是修改/etc/sysctl.conf文件,保證里面含有以下三行配置:

配置型 2.1

    #表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0,表示關閉  
    net.ipv4.tcp_tw_reuse = 1  
    #表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉  
    net.ipv4.tcp_tw_recycle = 1  
    #表示系統同時保持TIME_WAIT的最大數量,如果超過這個數字,
    #TIME_WAIT將立刻被清除并打印警告信息。默認為180000,改為5000。
    net.ipv4.tcp_max_tw_buckets = 5000

關于重用`TIME_WAIT`狀態的句柄的操作,也可以在代碼中設置:

代碼片段2.1

int on = 1;
if (setsockopt(socketfd/*socket句柄*/,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on)))
{
    return ERROR_SET_REUSE_ADDR;
}

如果在代碼中設置了關于重用的操作,程序中將使用代碼中設置的選項決定重用或者不重用,/etc/sysctl.conf中`net.ipv4.tcp_tw_reuse`中的設置將不再其作用。

當然這樣設置是有悖TCP的設計標準的,因為處于`TIME_WAIT`狀態的TCP連接,是有其存在的積極作用的,前面已經介紹過。假設客戶端的ACK N+1信號發送失敗,服務器端在1MSL時間過后會重發FIN N信號,而此時客戶端重用了之前關閉的連接句柄建立了新的連接,但是此時就會收到一個FIN信號,導致自己被莫名其妙關閉。

一般`TIME_WAIT`會維持在2MSL(linux下1MSL默認為30秒)時間,但是這個時間可以通過代碼修改:

代碼片段2.2

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 10;
if (setsockopt(socketfd,SOL_SOCKET,SO_LINGER,(char *)&so_linger,sizeof(struct linger)))
{
    return ERROR_SET_LINGER;
}

這里代碼將`TIME_WAIT`的時間設置為10秒(在BSD系統中,將會是0.01*10s)。TCP中的`TIME_WAIT`機制使得socket程序可以“優雅”的關閉,如果你想你的程序更優雅,最好不要設置`TIME_WAIT`的停留時間,讓老的tcp數據包在合理的時間內自生自滅。當然對于`SO_LINGER`參數,它不僅僅能夠自定義`TIME_WAIT`狀態的時間,還能夠將TCP的四次揮手直接禁用掉,假設對于so_linger結構體變量的設置是這個樣子的:

    so_linger.l_onoff = 1;
    so_linger.l_linger = 0;

如果客戶端的socket是這么設置的那么socket的關閉流程就直接是這個樣子了:

socket連接關閉問題的示例分析

這相當于客戶端直接告訴服務器端,我這邊異常終止了,對于我稍后給出的所有數據包你都可以丟棄掉。服務器端如果接受到這種RST消息,會直接把對應的socket句柄回收掉。有一些socket程序不想讓TCP出現`TIME_WAIT`狀態,會選擇直接使用RST方式關閉socket,以保證socket句柄在最短的時間內得到回收,當然前提是接受有可能被丟棄老的數據包這種情況的出現。如果socket通信的前后數據包的關聯性不是很強的話,換句話說每次通信都是一個單獨的事務,那么可以考慮直接發送RST信號來快速關閉連接。

補充

1.文中提到的修改/etc/sysctl.conf文件的情況,修改完成之后需要運行`/sbin/sysctl -p`后才能生效。

2.圖1中發送完FIN M信號后,被動關閉端的socket程序中輸入流會接收到一個EOF標示,是在C代碼中處理時recv函數返回0代表對方關閉,在java代碼中會在InputStream的read函數中接收到-1:

代碼片段3.1

Socket client = new Socket();//,9090
    try {
        client.connect(
            new InetSocketAddress("192.168.56.101",9090));
        while(true){                
            int c = client.getInputStream().read();
            if (c > 0) {
                System.out.print((char) c);
            } else {//如果對方socket關閉,read函數返回-1
                break;
            }
             try {
                Thread.currentThread().sleep(2000);                 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } catch (IOException e2) {
        e2.printStackTrace();
    } finally {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.如果主動關閉方已經發起了關閉的FIN信號,被動關閉方不予理睬,依然往主動關閉方發送數據,那么主動關閉方會直接返回RST新號,連接雙方的句柄就被雙方的操作系統回收,如果此時雙方的路由節點之前還存在未到達的數據,將會被丟棄掉。

4.通信的過程中,socket雙發中有一方的進程意外退出,則這一方將向其對應的另一方發送RST消息,所有雙發建立的連接將會被回收,未接收完的消息就會被丟棄。

以上是“socket連接關閉問題的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

信宜市| 剑河县| 枝江市| 深水埗区| 凯里市| 民权县| 盖州市| 资阳市| 鄂伦春自治旗| 灵武市| 潢川县| 施秉县| 故城县| 社旗县| 平昌县| 堆龙德庆县| 海宁市| 个旧市| 乌兰县| 金秀| 高平市| 济阳县| 灌云县| 修武县| 抚州市| 阿坝县| 偃师市| 冀州市| 互助| 台北市| 哈巴河县| 小金县| 大宁县| 中方县| 双城市| 贡嘎县| 吕梁市| 龙岩市| 宁陕县| 尼勒克县| 胶州市|