在 TCP 協(xié)議中,默認(rèn)情況下,當(dāng)我們調(diào)用 close() 函數(shù)關(guān)閉套接口時(shí),TCP 走四次揮手進(jìn)行斷開鏈路,但是要是若緩沖區(qū)還有數(shù)據(jù)未發(fā)送到對(duì)端時(shí),系統(tǒng)將嘗試把這些數(shù)據(jù)發(fā)送給對(duì)端。四次揮手的過程導(dǎo)致我們?cè)?TIME_WAIT 狀態(tài)下無法復(fù)用端口。有些情況下我們不需要 TIME_WAIT, 而是想快速斷開連接,從而避免 socket 的堆積。
這個(gè)時(shí)候我們可以使用 SO_LINGER 套接字選項(xiàng)
struct linger {
int l_onoff;
int l_linger;
}
- 若 l_onoff 為0, 表示關(guān)閉該選項(xiàng)。l_linger 值被忽略,也即是走TCP 的默認(rèn)設(shè)置。
2)若 l_onoff 為非 0 且 l_linger 為 0,那么當(dāng) close 某個(gè)連接時(shí) TCP 將終止該連接。也即是TCP將丟棄保留在套接字發(fā)送緩沖區(qū)中的任何數(shù)據(jù),并發(fā)送RST報(bào)文給對(duì)端,不再走四次揮手,從而避免了 TCP 的 TIME_WAIT 狀態(tài)。但是依然存在以下可能性:在 2 MSL 秒內(nèi)創(chuàng)建該連接的另一個(gè)化身,導(dǎo)致來自剛被終止的連接上的舊的重復(fù)分節(jié)被不正確的傳遞到新的化身上。
3)若 l_onoff 為非 0 值且 l_linger 也為非 0 值,那么當(dāng)套接字關(guān)閉時(shí)內(nèi)核將拖延一段時(shí)間關(guān)閉,也即是若在套接字的發(fā)送緩沖區(qū)中還有殘留數(shù)據(jù),那么進(jìn)程將投入睡眠,直到數(shù)據(jù)發(fā)送完且均被對(duì)端確認(rèn)或者滯留時(shí)間到。若套接字被設(shè)置成非阻塞型,那么它將不等待 close 完成,即是滯留時(shí)間不為 0 也是如此。當(dāng)使用 SO_LINGER 選項(xiàng)時(shí),應(yīng)用程序檢查 close 的返回值很重要,因?yàn)槿粼跀?shù)據(jù)發(fā)送完并被確認(rèn)前延滯時(shí)間到的話,close 將返回 EWOULDBLOCK 錯(cuò)誤,且套接字發(fā)送緩沖區(qū)中的任何殘留數(shù)據(jù)都被丟棄。
通過下面實(shí)現(xiàn)進(jìn)行驗(yàn)證。
首先 server 端使用 nc 進(jìn)行監(jiān)聽一個(gè)TCP 指定端口。
客戶端使用如下代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
struct sockaddr_in peer;
struct linger linger;
int ret;
int sock = socket(AF_INET, SOCK_STREAM, 0);
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &peer.sin_addr);
peer.sin_port = htons(atoi(argv[2]));
memset(&linger, 0, sizeof(linger));
linger.l_onoff = 1;
linger.l_linger = 0;
ret = setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
if (ret) {
printf("Fail to set linger\\n");
exit(1);
}
ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer));
if (ret) {
printf("Fail to connect.\\n", strerror(errno));
exit(1);
}
printf("Connect successfully\\n");
close(sock);
printf("Done\\n");
return 0;
}
通過抓包分析來看,調(diào)用 close 后,客戶端直接發(fā)送了 RST 報(bào)文端開了連接。
19:22:13.101476 IP 17.15.220.199 > localhost.localdomain : Flags [S], seq 12771346 ..
19:22:13.101509 IP localhost.localdomain > 17.15.220.199 : Flags [S .], seq 1277234 ..
19:22:13.101732 IP 17.15.220.199 > localhost.localdomain : Flags [.], ack ...
19:22:13.101912 IP 17.15.220.199 > localhost.localdomain : Flags [R .] ...
在 tcp_close 中查看具體實(shí)現(xiàn)
/*
內(nèi)核并并不關(guān)心有多少數(shù)據(jù)未被用戶進(jìn)程讀取,內(nèi)核關(guān)心的是有沒有數(shù)據(jù)未被讀取,
若有數(shù)據(jù)未被讀取而丟棄(data_was_unread>0),則給對(duì)方發(fā)送rst報(bào)文
若沒有數(shù)據(jù)未被用戶進(jìn)程讀取,也即是全部數(shù)據(jù)都被用戶進(jìn)程讀取了(data_was_unread==0),則相對(duì)對(duì)端發(fā)送fin報(bào)文
*/
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);
/*發(fā)送rst報(bào)文前設(shè)置狀態(tài)為TCP_CLOSE,這時(shí)沒有TIME_WAIT狀態(tài),沒有FIN_WAIT_1狀態(tài),說明此時(shí)時(shí)不正常關(guān)閉的。
所以可得,在編寫程序時(shí),在關(guān)閉連接前,一定要保證所有接收到的數(shù)據(jù)被讀取,否則連接會(huì)不正常關(guān)閉*/
tcp_set_state(sk, TCP_CLOSE);
//發(fā)送rst報(bào)文,之所以不是fin報(bào)文,是因?yàn)殛P(guān)閉時(shí)還有未讀的數(shù)據(jù)屬于異常情況,fin表示一切正常情況
tcp_send_active_reset(sk, GFP_KERNEL);
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
/* Check zero linger _after_ checking for unread data. */
/*調(diào)用tcp_disconnect斷開、刪除并釋放已建立連接但未被accept的傳輸控制塊,同時(shí)
刪除并釋放已接收在接收隊(duì)列(包括失序隊(duì)列)上的段以及發(fā)送隊(duì)列上的段*/
sk->sk_prot->disconnect(sk, 0);// tcp_disconnect
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONDATA);
} else if (tcp_close_state(sk)) { //若未讀字節(jié)數(shù)為0,則調(diào)用tcp_close_state根據(jù)sk當(dāng)前狀態(tài)來設(shè)置sk下一狀態(tài),比如當(dāng)前狀態(tài)為TCP_ESTABLISHED,則下一狀態(tài)為TCP_FIN_WAIT1,該方法的返回確定是否發(fā)送fin報(bào)文給對(duì)方
/*
從上面的代碼段可以看到,當(dāng)有數(shù)據(jù)還未讀取時(shí),說明是異常關(guān)閉,直接發(fā)送 RST 報(bào)文給對(duì)端。若接收緩沖區(qū)中數(shù)據(jù)都已經(jīng)讀取完了,判斷 SOCK_LINGER 套接字選項(xiàng),若 l_linger 為 0,則調(diào)用 tcp_disconnect 給對(duì)端發(fā)送 RST 報(bào)文,同時(shí)釋放接收和發(fā)送隊(duì)列上的數(shù)據(jù)。
審核編輯:劉清
-
RST
+關(guān)注
關(guān)注
0文章
31瀏覽量
7402 -
TCP協(xié)議
+關(guān)注
關(guān)注
1文章
91瀏覽量
12078
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論