99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

什么是CLOSING狀態(tài)

科技綠洲 ? 來源:Linux開發(fā)架構之路 ? 作者:Linux開發(fā)架構之路 ? 2023-11-13 11:08 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

很多資料講了關于TCP的CLOSING和CLOSE_WAIT狀態(tài)以及所謂的優(yōu)雅關閉的細節(jié),多數(shù)側重與Linux的內核實現(xiàn)(除了《UNIX網絡編程》)。本文不注重代碼細節(jié),只關注邏輯。所使用的工具,tcpdump,packetdrill以及ss。

關于ss可以先多說幾句,它展示的信息跟netstat差不多,只不過更加詳細。netstat的信息是通過procfs獲取的,本質上來講就是遍歷/proc/net/netstat文件的內容,然后將其組織成可讀的形式展示出來,然而ss則可以針對特定的五元組信息提供更加詳細的內容,它不再通過procfs,而是用過Netlink來提取特定socket的信息,對于TCP而言,它可以提取到甚至tcp_info這種詳細的信息,它包括cwnd,ssthresh,rtt,rto等。

本文展示的邏輯使用了以下三樣工具:

1).packetdrill

使用packetdrill構造出一系列的包序列,使得TCP進入CLOSING狀態(tài)或者CLOSE_WAIT狀態(tài)。

2).tcpdump/tshark

抓取packetdrill注入的數(shù)據包以及協(xié)議棧反饋的包,以確認數(shù)據包序列確實如TCP標準所述的那樣。

3).ss/netstat

通過ss抓取packetdrill相關套接字的tcp_info,再次確認細節(jié)。

我想,我使用上述的三件套解析了CLOSING狀態(tài)之后,接下來的CLOSE_WAIT狀態(tài)就可以當作練習了。

我來一個一個說。

1.關于CLOSING狀態(tài)

首先我來描述一下而不是細說概念。

什么是CLOSING狀態(tài)呢?我們來看一下下面的局部狀態(tài)圖:

圖片

也就是說,當兩端都主動發(fā)送FIN的時候,并且在收到對方對自己發(fā)送的FIN之前收到了對方發(fā)送的FIN的時候,兩邊就都進入了CLOSING狀態(tài),這個在狀態(tài)圖上顯示的很清楚。這個用俗話說就是”同時關閉“。時序圖我就不給出了,請自行搜索或者自己畫。

有很多人都說,這種狀態(tài)的TCP連接在系統(tǒng)中存在了好長時間并百思不得其解。這到底是為什么呢?通過狀態(tài)圖和時序圖,我們知道,在進入CLOSING狀態(tài)后,只要收到了對方對自己的FIN的ACK,就可以雙雙進入TIME_WAIT狀態(tài),因此,如果RTT處在一個可接受的范圍內,發(fā)出的FIN會很快被ACK從而進入到TIME_WAIT狀態(tài),CLOSING狀態(tài)應該持續(xù)的時間特別短。

以下是packetdrill腳本,很簡單的一個腳本:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0

0.100 < S 0:0(0) win 32792 < mss 1460,sackOK,nop,nop,nop,wscale 7 >
0.100 > S. 0:0(0) ack 1 win 5840 < mss 1460,nop,nop,sackOK,nop,wscale 7 >
0.200 < . 1:1(0) ack 1 win 257
0.200 accept(3, ..., ...) = 4

// 象征性寫入一些數(shù)據,裝的像一點一個正常的TCP連接:握手-傳輸-揮手
0.250 write(4, ..., 1000) = 1000

0.300 < . 1:1(0) ack 1001 win 257

// 主動斷開,發(fā)送FIN
0.400 close(4) = 0
// 在未對上述close的FIN進行ACK前,先FIN
0.500 < F. 1:1(0) ack 1001 win 260
// 至此,成功進入同時關閉的CLOSING狀態(tài)。

// 由于packetdrill不能用dup調用,也好用多線程,為了維持進程不退出,只能等待
10000.000 close(4) = 0

同時,我啟用tcpdump抓包,確認了TCP狀態(tài)圖的細節(jié),即,還沒有收到對方對FIN的ACK時,收到了對方的FIN:

圖片

有個異常,沒有收到FIN的ACK(packetdrill沒有回復,這正常,因為腳本里本來就沒有這個語句),然而也沒有看到重傳,此時該連接應該是處于CLOSING狀態(tài)了,用ss來確認:

CLOSING 1 1 192.168.0.1:webcache 192.0.2.1:54442

cubic wscale:7,7 rto:2000 rtt:50/25 ssthresh:2 send 467.2Kbps rcv_space:5840

果然,進入了CLOSING狀態(tài)且沒有消失,時不我待,當過了2秒以后,ss的結果變成了:

CLOSING 1 1 192.168.0.1:webcache 192.0.2.1:54442

cubic wscale:7,7 rto:4000 rtt:50/25 ssthresh:2 send 467.2Kbps rcv_space:5840

明顯在退避!如果繼續(xù)觀察,你會發(fā)現(xiàn)rto退避到了64秒之多。在我的場景中,CLOSING狀態(tài)的套接字維持了兩分鐘之久。

然而,為什么呢?為什么CLOSING狀態(tài)會維持這么久?為什么它沒有繼續(xù)維持下去直到永久呢?

很明顯,一端的FIN發(fā)出去后,沒有收到ACK,因此會退避重發(fā),知道4次退避,即22222*2秒之久?,F(xiàn)在的問題是,為什么重發(fā)FIN始終不成功呢?要是成功了的話,估計ACK瞬間也就回來了,那么CLOSING狀態(tài)也就可以進入TIME_WAIT了,但是沒有成功重傳FIN!

到此為止,我們知道,進入CLOSING狀態(tài)之后,兩邊都會等待接收自己FIN的ACK,一旦收到ACK,就會進入TIME_WAIT,如此反復,如果收不到ACK,則會不斷重傳FIN,直到忍無可忍,將socket銷毀?,F(xiàn)在,我們集中于解釋為什么重傳沒有成功,但是請記住,并不是每次都這樣,只是在我這個packetdrill構造的場景中會有重傳不成功,不然如果大概率不成功的話。豈不是每個CLOSING狀態(tài)都要維持很長時間????!

在我的場景下,通過hook重傳函數(shù)以及抓包確認,發(fā)現(xiàn)所有的重傳雖然退避了,但是都沒有真正將數(shù)據包發(fā)送出去,究其原因,最終確認問題出在以下代碼上:

if (atomic_read(&sk- >sk_wmem_alloc) >
    min(sk- >sk_wmem_queued + (sk- >sk_wmem_queued > > 2), sk- >sk_sndbuf))
    return -EAGAIN;

在Linux協(xié)議棧的實現(xiàn)中,tcp_retransmit_skb由tcp_retransmit_timer調用,即便是這里出了些問題沒有重傳成功,也還是會退避的,退避超時到期后,繼續(xù)在這里出錯,直到”不可容忍“銷毀socket。

我們可以得知,不管如何CLOSING狀態(tài)的TCP連接即便沒有收到對自己FIN的ACK,也不會永久保持下去,保持多久取決于自己發(fā)送FIN時刻的RTT,然后RTT計算出的RTO按照最大的退避次數(shù)來退避,直到最終執(zhí)行了固定次數(shù)的退避后,算出來的那個比較大的超時時間到期,然后TCP socket就銷毀了。

因此,CLOSING狀態(tài)并不可怕,起碼,不管怎樣,它有一個可控的銷毀時限。

...

現(xiàn)在我來解釋重傳不成功的細節(jié)。

我們知道,根據上述的代碼段,sk_wmem_alloc要足夠大,大到它比sk_wmem_queued+sk_wmem_queued/4更大的時候,才會返回錯誤造成重傳不成功,然而我們的packetdrill腳本中構造的TCP連接的生命周期中僅僅傳輸了1000個字節(jié)的數(shù)據,并且這1000個字節(jié)的數(shù)據得到了ACK,然后就結束了連接。一個socket保有一個sk_wmem_alloc字段,在skb交給這個socket的時候,該字段會增加skb長度的大小(skb本身大小包括skb數(shù)據大小),然而當skb不再由該socket持有的時候,也就是其被更底層的邏輯接管之后,socket的sk_wmem_alloc字段自然會減去skb長度的大小,這一切的過程由以下的函數(shù)決定,即skb_set_owner_w和skb_orphan。我們來看一下這兩個函數(shù):

static inline void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
{
    skb_orphan(skb);
    skb- >sk = sk;
    // sock_wfree回調中會遞減sk_wmem_alloc相應的大小,其大小就是skb- >truesize
    skb- >destructor = sock_wfree;
    /*
     * We used to take a refcount on sk, but following operation
     * is enough to guarantee sk_free() wont free this sock until
     * all in-flight packets are completed
     */
    atomic_add(skb- >truesize, &sk- >sk_wmem_alloc);
}
static inline void skb_orphan(struct sk_buff *skb)
{
    // 調用回調函數(shù),遞減sk_wmem_alloc
    if (skb- >destructor)
        skb- >destructor(skb);
    skb- >destructor = NULL;
    skb- >sk        = NULL;
}

也就是說,只要skb_orphan在skb通向網卡的路徑上被正確調用,就會保證sk_wmem_alloc的值隨著skb進入socket的管轄時而增加,而被實際發(fā)出后而減少。但是根據我的場景,事實好像不是這樣,sk_wmem_alloc的值只要發(fā)送一個skb就會增加,絲毫沒有減少的跡象...這是為什么呢?

有的時候,當你對某個邏輯理解足夠深入后,一定要相信自己的判斷,內核存在BUG!內核并不完美。我使用的是2.6.32老內核,這個內核我已經使用了6年多,這是我在這個內核上發(fā)現(xiàn)的第4個BUG了。

請注意,我的這個場景中,我使用了packetdrill來構造數(shù)據包,而packetdrill使用了tun網卡。為什么使用真實網卡甚至使用loopback網卡就不會有問題呢?這進一步引導我去調查tun的代碼,果不其然,在其hard_xmit回調中沒有調用skb_orphan!也就說說,但凡使用2.6.32內核版本tun驅動的,都會遇到這個問題呢。在tun的xmit中加入skb_orphan之后,問題消失,抓包你會發(fā)現(xiàn)大量的FIN重傳包,這些重傳隨著退避而間隔加大(注意,用ss命令比對一下rto字段的值和tcpdump抓取的實際值):

圖片

(為了驗證這個,我修改了packetdrill腳本,中間增加了很多的數(shù)據傳輸,以便盡快重現(xiàn)sk_wmem_alloc在使用tun時不遞減的問題)于是,我聯(lián)系了前公司的同事,讓他們修改OpenVPN使用的tun驅動代碼,因為當時確實出現(xiàn)過關于TCP使用OpenVPN隧道的重傳問題,然而,得到的答復卻是,xmit函數(shù)中已經有skb_orphan了...然后我看了下代碼,發(fā)現(xiàn),公司的代碼已經不存在問題了,因為我在前年搞tun多隊列的時候,已經移植了3.9.6的tun驅動,這個問題已經被修復。

自己曾經做的事情,已然不再憶起...

2.關于CLOSE_WAIT狀態(tài)

和CLOSING狀態(tài)不同,CLOSE_WAIT狀態(tài)可能會持續(xù)更久更久的時間,導致無用的socket無法釋放,這個時間可能與應用進程的生命周期一樣久!

我們先看一下CLOSE_WAIT的局部狀態(tài)圖。

圖片

然后我來構造一個packetdrill腳本:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0

0.100 < S 0:0(0) win 32792 < mss 1460,sackOK,nop,nop,nop,wscale 7 >
0.100 > S. 0:0(0) ack 1 win 14600 < mss 1460,nop,nop,sackOK,nop,wscale 7 >
0.200 < . 1:1(0) ack 1 win 257
0.200 accept(3, ..., ...) = 4
// 什么也不發(fā)了,直接斷開
0.350 < F. 1:1(0) ack 1 win 260
// 協(xié)議棧會對這個FIN進行ACK,然則應用程序不關閉連接的話...
//0.450 close(4) = 0
// 該連接就會變成CLOSE_WAIT,并且只要其socket引用計數(shù)不為0,就一直僵死在那里
2000.000 close(4) = 0

同樣的,我來展示抓包結果:

圖片

最后,和描述CLOSING狀態(tài)不同的是,隔了N個小時之后,我來看ss -ip的結果:

CLOSE-WAIT 1 0 192.168.0.1:webcache 192.0.2.1:53753 users:(("ppp",2399,8))

cubic wscale:7,7 rto:300 rtt:100/50 ato:40 cwnd:10 send 1.2Mbps rcv_space:14600

這個CLOSE_WAIT還在!這是為什么呢?

很遺憾,上述的packetdrill腳本并不能直觀地展示這個現(xiàn)象,還得靠我說一說。

CLOSE_WAIT是一端在收到FIN之后,發(fā)送自己的FIN之前所處的狀態(tài),那么很顯然,如果一個進程/線程始終不發(fā)送FIN,那么在該連接所隸屬的socket的生命周期內,這個socket就會一直存在,我們知道,在UNIX/Linux/WinSock中,socket作為一個描述符出現(xiàn),只要進程/線程繼續(xù)持有它,它就會一直存在,因此大多數(shù)情況下進程/線程的生命周期內,此TCP套接字就會始終處在CLOSE_WAIT狀態(tài)。進程/線程長時間持有不需要的socket描述符,更多的并不是有意的,而是在進行諸如fork/clone之類的系統(tǒng)調用后,dup了父親的文件描述符,然后在孩子那里又沒有及時關閉,另外的原因就是編程者對socket描述符的close接口以及shutdown接口不是很理解了。

現(xiàn)在,我們用一個問題來繼續(xù)我們的討論。

什么時候進程在超長的生命周期內不會如愿關閉TCP從而發(fā)送FIN呢?

我的答案比較直接:不能指望close會發(fā)送FIN!

相信很多人在想斷開一個TCP連接的時候,都會調用close吧。并且這種做法幾乎都是正確的,以至于很多人都把這作為一種標準的做法。但是這是不對的!Why?!在《UNIX網絡編程》中,曾經提到了所謂的”優(yōu)雅關閉TCP連接“,何謂優(yōu)雅??!如果你充分理解close,shutdown,應該就會知道,CLOSE_WAIT出現(xiàn),你應該可以給出一些解釋。

close調用

close的參數(shù)只是一個文件描述符號,它不理解這個文件真正的細節(jié),它只是一個文件系統(tǒng)內范疇的一個調用,它只是關閉文件描述符,保證此進程不會在讀取它而已。如果你關閉了文件描述符4,即close(4),你知道4代表的文件會作何反應嗎??文件系統(tǒng)并不知道4號描述符代表的文件到底是什么,更不知道有多少進程共享這個底層的”實體“,所以一個進程層面上邏輯根本沒有權力去徹底關閉一個socket。如果你想了解close的細節(jié),更應該去看看UNIX文件抽象或者文件系統(tǒng)的細節(jié),而不是socket。請參見位于fs/open.c中的:

SYSCALL_DEFINE1(close, unsigned int, fd)
{
    ...
    fdt = files_fdtable(files);
    ...
    filp = fdt- >fd[fd];
    ...
    retval = filp_close(filp, files);
    ...
    return retval;
    ...
}
EXPORT_SYMBOL(sys_close);

在filp_close中會有fput調用:

void fput(struct file *file)
{
    if (atomic_long_dec_and_test(&file- >f_count))
        __fput(file);
}

看到那個引用計數(shù)了嗎?只有當這個文件的引用計數(shù)變成0的時候,才會調用底層的關閉邏輯,對于socket而言,如果仍然還有一個進程或者線程持有這個socket對應的文件系統(tǒng)的描述符,那么即便你調用了close,也不會進入了socket的close邏輯,它在文件系統(tǒng)層面就返回了!

shutdown調用

這個才是真正關閉一個TCP連接的調用!shutdown并沒有文件系統(tǒng)的語義,它專門針對內核層的TCP socket。因此,調用shutdown的邏輯,才是真正關閉了與之共享信道的tcp socket。

所謂的優(yōu)雅關閉,就是在調用close之前, 首先自己調用shutdown(RD or WD)。這樣的時序才是關閉TCP的必由之路!

如果你想優(yōu)雅關閉一個TCP連接,請先用shutdown,然后后面跟一個close。不過有點詭異的是,Linux的shutdown(SHUT_RD)貌似沒有任何效果,不過這無所謂了,本來對于讀不讀的,就不屬于TCP的范疇,只有SHUT_WR才會實際發(fā)送一個FIN給對方。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據
    +關注

    關注

    8

    文章

    7257

    瀏覽量

    91942
  • 代碼
    +關注

    關注

    30

    文章

    4900

    瀏覽量

    70798
  • 腳本
    +關注

    關注

    1

    文章

    398

    瀏覽量

    28495
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    CC3200 socket狀態(tài)如何查詢?

    如對一個TCP server來說,我怎么知道這個Socket處于什么狀態(tài)呢?Tcp Socket大概有如下狀態(tài),但是我在TI的SDK中沒找到相關的定義。附:NETSTAT TCP套接字解釋
    發(fā)表于 05-06 11:00

    是否有一些命令知道LAN820板的狀態(tài)

    ,ping不工作,但2 LED仍然閃爍。PIC代碼仍然完美地工作,我可以通過串口驗證它。我想知道是否有一些命令知道LAN820板的狀態(tài)?我試圖檢測何時拔出RJ45,但它不起作用。我很喜歡這個,謝謝你的幫助
    發(fā)表于 10-26 16:14

    關閉34908A繼電器并保持關閉狀態(tài)

    )?2。如何關閉一個多路復用器的繼電器,比如說(@ 101)并在掃描和測量(@ 201:240)的直流電壓時保持關閉狀態(tài)?我使用以下命令測試了一張卡的關閉一個繼電器(并保持關閉以便進行測量):ROUT
    發(fā)表于 07-30 06:39

    UC3901/UC2901/UC1901 pdf datas

    The UC1901 family is designed to solve many of the problems associatedwith closing a feedback
    發(fā)表于 09-14 01:35 ?16次下載

    水電機組的狀態(tài)監(jiān)測及狀態(tài)檢修

    該文介紹水電機組在線監(jiān)測技術以及狀態(tài)檢修系統(tǒng)的組成結構,提出水電廠開展狀態(tài)檢修的I作思路。
    發(fā)表于 04-07 15:15 ?16次下載

    狀態(tài)機舉例

    狀態(tài)機舉例 你可以指定狀態(tài)寄存器和狀態(tài)機的狀態(tài)。以下是一個有四種狀態(tài)的普通狀態(tài)機。 // Th
    發(fā)表于 03-28 15:18 ?1097次閱讀

    MC33972抑制喚醒多路開關檢測接口

    The 33972 Multiple Switch Detection Interface with suppressed wake-up is designed to detect the closing and opening of up to 22 switch contacts.
    發(fā)表于 09-19 12:53 ?21次下載
    MC33972抑制喚醒多路開關檢測接口

    基于設備狀態(tài)的網絡狀態(tài)評估方案

    當前通信網絡的異構性較強、兼容性較差,網絡狀態(tài)的評估受到極大限制,技術與市場等因素導致網絡狀態(tài)評估標準難以統(tǒng)一。本體具有良好的開放性與可擴展性,能很好地承載知識的形式化,有助于推動標準的統(tǒng)一。采用
    發(fā)表于 01-18 17:05 ?0次下載

    Richard Gerber致閉幕詞

    Closing Remarks with Richard Gerber
    的頭像 發(fā)表于 10-26 07:12 ?2240次閱讀

    狀態(tài)模式(狀態(tài)機)

    以前寫狀態(tài)機,比較常用的方式是用 if-else 或 switch-case,高級的一點是函數(shù)指針列表。最近,看了一文章《c語言設計模式–狀態(tài)模式(狀態(tài)機)》(來源:embed linux
    發(fā)表于 12-16 16:53 ?9次下載
    <b class='flag-5'>狀態(tài)</b>模式(<b class='flag-5'>狀態(tài)</b>機)

    linux 中 ACPI 電源管理 G 狀態(tài)、S 狀態(tài)、D 狀態(tài)、C 狀態(tài)、P 狀態(tài)

    ACPI 高級電源管理ACPI 中定義了 G、D、S、C、P 這 5 個大的電力狀態(tài)。G 狀態(tài) Global system stateG 狀態(tài)表示的是用戶看到的整個系統(tǒng)的電力狀態(tài)。G0
    發(fā)表于 01-05 14:12 ?4次下載
    linux 中 ACPI 電源管理 G <b class='flag-5'>狀態(tài)</b>、S <b class='flag-5'>狀態(tài)</b>、D <b class='flag-5'>狀態(tài)</b>、C <b class='flag-5'>狀態(tài)</b>、P <b class='flag-5'>狀態(tài)</b>

    HTTP的狀態(tài)消息

     HTTP狀態(tài)消息是指HTTP服務器在響應客戶端請求時返回的狀態(tài)信息。狀態(tài)消息由數(shù)字狀態(tài)碼和可選的文本描述組成,主要有以下幾種類型
    發(fā)表于 05-06 16:01 ?661次閱讀

    就緒狀態(tài)和等待狀態(tài)的區(qū)別

    就緒狀態(tài)和等待狀態(tài)是計算機領域中一對常用的術語,用于描述進程或線程在執(zhí)行時的不同狀況。下面我將詳細解釋就緒狀態(tài)和等待狀態(tài)的區(qū)別。 就緒狀態(tài)
    的頭像 發(fā)表于 11-17 11:29 ?4048次閱讀

    阻塞狀態(tài)和等待狀態(tài)的區(qū)別

    阻塞狀態(tài)和等待狀態(tài)是計算機領域中常用的術語,用來描述進程或線程的狀態(tài)。盡管這兩個狀態(tài)在表面上有些相似,但它們有著本質上的區(qū)別。本文將詳盡、詳實、細致地討論阻塞
    的頭像 發(fā)表于 11-17 11:33 ?5007次閱讀

    時序邏輯電路中如何判斷有效狀態(tài)和無效狀態(tài)

    在時序邏輯電路中,有效狀態(tài)和無效狀態(tài)的判斷是電路分析和設計的重要環(huán)節(jié)。有效狀態(tài)是指電路在實際工作過程中被利用到的狀態(tài),它們構成了電路的有效循環(huán);而無效
    的頭像 發(fā)表于 08-12 15:51 ?5191次閱讀