TCP異常處理(accept返回前連線中止)與SO_LINGER選項
一、accept返回前終止分析
問題一:
因為accept是堵塞的, 並且等待來自客戶端的連線, 但是, 如果在accept期間 , 如果因為系統呼叫中斷了accept就會返回一個非致命的錯誤, 而此時有來自客戶端進行TCP三路握手完成後, 而我們通過迴圈在此呼叫accept函式可以完成連線,。因為TCP的連線是在核心中完成的, 與accept函式的執行無關。
問題二:
如果我們在呼叫accept函式返回之前, 該客戶端TCP傳送了一個RST(復位)。在伺服器中, 表現為該連線仍在TCP佇列中, 等待伺服器程序呼叫accept的時候RST到達。此時返回的套接字是一個已連線,但是卻有接受了RST的套接字。
模型圖如下:
*基於不同系統的處理方式如下:
1、源自Berkeley是完全有核心中斷連線, 不返回給我程序9
2、大多數的SVR4實現返回一個錯誤給伺服器程序, 作為accept的返回結果, 錯誤取決去實現的本身。SVR4中errno返回EPROTO, POSIX指出返回ECONNABORTED(軟體引起的連線中斷),
3、而非致命錯誤(系統中斷)的時候, 我們可以再次呼叫accept返回連線套接字。
實驗如下:
客戶端程式在建立呼叫connect函式後, 立刻呼叫設定SO_LINGER套接字選項產生一個RST傳送給對端。
而伺服器程式在socket bind listen後, 睡眠睡眠一段時間, 然後呼叫accept函式, 來模擬accept函式連線前中斷。
執行方案:在當伺服器在sleep,睡眠5s期間呼叫客戶端程式, 然後5s後伺服器處理完連線後進入一下迴圈, 繼續睡眠5s, 此時呼叫ctri +C退出程式
伺服器程式部分核心程式碼:
客戶端程式部分核心程式碼:for(;;){ int confd; len = sizeof(cliaddr); sleep(5); fprintf(stdout, "accept...\n"); confd = accept(sockfd, (struct sockaddr *) &cliaddr, &len); fprintf(stderr, "%s\n", strerror(errno)); int n = read(confd, recvbuf, MAXLINE); fprintf(stderr, "%s\n", strerror(errno)); close(confd); }
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
lger.l_onoff = 1;
lger.l_linger = 0;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lger, sizeof(lger));
close(sockfd);
執行結果如下:
通過列印錯誤可以:程式accept照樣接收套接字, 沒有遵循Berkeley的處理原則, 而是SVR4 處理(根據作業系統不同, 核心處理方式不同)。
抓包結果如下:
二、SO_LINGER選項分析
SO_LINGER選項用以下資料結構來改變:struct linger{
int l_onoff; // 0 = off, nozero = on
int l_linger; // linger time
};
1、l_onoff = 0, 表示該選項關閉, l_linger值將會被忽略。close呼叫後, 將會立刻返回給呼叫則如下圖1所示
2、設定 l_onoff為非0,l_linger為0,則套介面關閉時TCP夭折連線,TCP將丟棄保留在套介面傳送緩衝區中的任何資料併發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態
3、l_onoff = nozero ,l_linger = nozero的時候, 該套接字關閉時核心將拖延一段時間(由l_linger覺得)。呼叫close函式,當套接字緩衝區還有資料的話, 程序將會進入睡眠狀態, 核心將會繼續傳送資料直到所有的資料都被對方確認完全接受。但是如果, l_linger時間到達, 而資料還沒有完全傳送完畢, 那麼剩下的緩衝區的資料將會被摒棄,close將會返回EWOULDBLOCK錯誤。若資料傳送完畢, 那麼將會告訴我們資料和FIN都已經被對方確認。如果套接字在非堵塞的情況以上的假設不存在
三、總結
1、瞭解accept異常中斷和rst
2、熟悉SO_LINGER在堵塞情況的應用和在非堵塞情況下的區別
3、SO_LINGER選項中中close的工作原理