動手學(xué)習(xí)TCP系列之4種定時器
上一篇中介紹了TCP數(shù)據(jù)傳輸中涉及的一些基本知識點。本文讓我們看看TCP中的4種定時器。
TCP定時器
對于每個TCP連接,TCP管理4個不同的定時器,下面看看對4種定時器的簡單介紹。
重傳定時器使用于當(dāng)希望收到另一端的確認(rèn)。
該定時器是用來決定超時和重傳的。
由于網(wǎng)絡(luò)環(huán)境的易變性,該定時器時間長度肯定不是固定值;該定時器時間長度的設(shè)置依據(jù)是RTT(Round Trip Time),根據(jù)網(wǎng)絡(luò)環(huán)境的變化,TCP會根據(jù)這些變化并相應(yīng)地改變超時時間。
堅持定時器(persist)使窗口大小信息保持不斷流動,即使另一端關(guān)閉了其接收窗口。
?;疃〞r器(keepalive)可檢測到一個空閑連接的另一端何時崩潰或重啟。
2MSL定時器測量一個連接處于TIME_WAIT狀態(tài)的時間。
參見"動手學(xué)習(xí)TCP:TCP特殊狀態(tài)"中對TIME_WAIT的介紹
下面就介紹一下堅持定時器和保活定時器。
堅持定時器
TCP通過讓接收方指明希望從發(fā)送方接收的數(shù)據(jù)字節(jié)數(shù)(即窗口大小)來進(jìn)行流量控制。
如果窗口大小為 0會發(fā)生什么情況呢?這將有效地阻止發(fā)送方傳送數(shù)據(jù),直到窗口變?yōu)榉?為止。
但是,由于TCP不對ACK報文段進(jìn)行確認(rèn)(TCP只確認(rèn)那些包含有數(shù)據(jù)的ACK報文段),如果上圖中通知發(fā)送方窗口大于0的[ACK]丟失了,則雙方就有可能因為等待對方而使連接死鎖。接收方等待接收數(shù)據(jù)(因為它已經(jīng)向發(fā)送方通告了一個非0的窗口),而發(fā)送方在等待允許它繼續(xù)發(fā)送數(shù)據(jù)的窗口更新。
為防止這種死鎖情況的發(fā)生,發(fā)送方使用一個堅持定時器 (persist timer)來周期性地向接收方查詢,以便發(fā)現(xiàn)窗口是否已增大。這些從發(fā)送方發(fā)出的報文段稱為窗口探查(window probe)。
實驗代碼
下面通過Python socket實現(xiàn)一個快的發(fā)送端和慢的接收端,然后通過Wireshark抓包來看看窗口更新通知和窗口探查。
客戶端代碼如下,用戶輸入字符,客戶端將用戶輸入重復(fù)1000次然后發(fā)送給服務(wù)端,通過這種簡單的重復(fù)來模擬一個快的發(fā)送端:
from socket import * import time HOST = "192.168.56.102" PORT = 8081 ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM) client.connect(ADDR) while True: input = raw_input() if input: client.send(input*1000) else: client.close() break
對于服務(wù)端,通過制定一個小的接收BUFFER,以及一個延時來模擬一個慢的接收端:
import sys from socket import * import time HOST = "192.168.56.102" PORT = 8081 BUFSIZ = 100 ADDR = (HOST, PORT) server = socket(AF_INET, SOCK_STREAM) print "Socket created" try: server.bind(ADDR) except error, msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit() server.listen(1) print 'Socket now listening' conn, addr = server.accept() while True: time.sleep(3) try: data = conn.recv(BUFSIZ) if data: print data else: conn.close() break except Exception, e: print e break
#p#
在開始運行代碼之前還需要進(jìn)行一些設(shè)置,默認(rèn)情況下接收端的window size很大,實驗中很難耗盡。
所以,為了看到實驗效果,需要對系統(tǒng)進(jìn)行一些設(shè)置。打開虛擬機中的注冊表設(shè)置"regedit",然后找到選項"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters",設(shè)置"TcpWindowSize"為4096Bytes。
注意,實驗結(jié)束后,一定要恢復(fù)"TcpWindowSize"的原始設(shè)置,不然可能會影響正常的網(wǎng)絡(luò)訪問。
關(guān)于更多TCP相關(guān)的注冊表設(shè)置,可以參考這個鏈接。
運行效果
下面運行代碼,分別輸入兩個字符"a"和"b",通過Wireshark可以看到,在進(jìn)行連接確認(rèn)的時候,接收端已經(jīng)給出了我們跟新后的可用窗口4096Bytes。
經(jīng)過***輪發(fā)送后,接收方的window size減少了1000;當(dāng)兩個數(shù)據(jù)包都處理完成后,window size又恢復(fù)到了4096。
第二輪測試中,發(fā)送端發(fā)送"1234567890"十個字符,從接收端的***一個[ACK]包可以看到,***接收端window size為1393,此次傳輸?shù)酱私Y(jié)束。
過了一段時間,當(dāng)慢接收端處理完數(shù)據(jù)之后,接收端會發(fā)送窗口更新,通知發(fā)送端可以窗口為4096Bytes。
第三輪測試中,發(fā)送端發(fā)送更多的字符"1234567890987654321",這次接收端的可用窗口就被耗盡了,然后接收端發(fā)送一個[TCP ZeroWindow]的通知;這時,發(fā)送端停止發(fā)送,然后通過發(fā)送窗口探查。
當(dāng)接收端有可用窗口的時候,接收端會發(fā)送窗口更新,數(shù)據(jù)傳輸繼續(xù)。
注意,[TCP ZeroWindowProbe]和[TCP ZeroWindowProbeAck]的Seq和Ack號。
糊涂窗口綜合癥
基于窗口的流量控制方案,會導(dǎo)致一種"糊涂窗口綜合癥SWS(Silly Window Syndrome)"的狀況。
當(dāng)發(fā)送端應(yīng)用進(jìn)程產(chǎn)生數(shù)據(jù)很慢、或接收端應(yīng)用進(jìn)程處理接收緩沖區(qū)數(shù)據(jù)很慢,或二者兼而有之;就會使應(yīng)用進(jìn)程間傳送的報文段很小,特別是有效載荷很小。 極端情況下,有效載荷可能只有1個字節(jié);而傳輸開銷有40字節(jié)(20字節(jié)的IP頭+20字節(jié)的TCP頭),加上物理幀頭后,有效的數(shù)據(jù)傳輸比例就更小了,這就浪費了網(wǎng)絡(luò)帶寬,表現(xiàn)為糊涂窗口綜合癥。
糊涂窗口綜合癥可能由接收端或者發(fā)送端引起,不同的起因需要不同的解決方案,更多內(nèi)容可以參考此處。
保活定時器
跟據(jù)TCP協(xié)議,當(dāng)發(fā)送端和接收端都不主動釋放一個TCP連接的時候,該連接將一直保持。即使一端出現(xiàn)了故障,由于另一端沒有收到任何通知,TCP連接也會一直保持,這樣就會造成TCP連接資源的浪費。
TCP keepalive
為了解決這個問題,大多數(shù)的實現(xiàn)中都是使服務(wù)器設(shè)置?;钣嫊r器。
保活計時器通常設(shè)置為2小時。若服務(wù)器過了2小時還沒有收到客戶的信息,它就發(fā)送探測報文段。若發(fā)送了10個探測報文段(每一個相隔75秒)還沒有響應(yīng),就假定客戶出了故障,因而就終止該連接。
在Linux系統(tǒng)中,有三個跟TCP keepalive相關(guān)的參數(shù):
tcp_keepalive_intvl (integer; default: 75; since Linux 2.4) The number of seconds between TCP keep-alive probes. tcp_keepalive_probes (integer; default: 9; since Linux 2.2) The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end. tcp_keepalive_time (integer; default: 7200; since Linux 2.2) The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep- alives are sent only when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.
在Socket編程中,可以通過設(shè)置"TCP_KEEPCNT","TCP_KEEPIDLE"和"TCP_KEEPINTVL"選項來更改上述的三個系統(tǒng)參數(shù):
from socket import * import time HOST = "192.168.56.102" PORT = 8081 ADDR = (HOST, PORT) client = socket(AF_INET, SOCK_STREAM) #TCP_KEEPCNT overwrite tcp_keepalive_probes,默認(rèn)9(次) #TCP_KEEPIDLE overwrite tcp_keepalive_time,默認(rèn)7200(秒) #TCP_KEEPINTVL overwrite tcp_keepalive_intvl,默認(rèn)75(秒) client.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) client.setsockopt(SOL_TCP, TCP_KEEPCNT, 5) client.setsockopt(SOL_TCP, TCP_KEEPINTVL, 5) client.setsockopt(SOL_TCP, TCP_KEEPIDLE, 10) client.connect(ADDR) while True: input = raw_input() if input: client.send(input*1000) else: client.close() break
#p#
TCP keepalive 包
下面是一段網(wǎng)絡(luò)上抓取的TCP keepalive包,接下來看看TCP keepalive包的內(nèi)容。
根據(jù)規(guī)范,TCP keepalive?;畎粦?yīng)該包含數(shù)據(jù),但也可以包含1個無意義的字節(jié),比如0x0。
TCP保活探測包Seq號是將前一個TCP包的Seq號減去1。
當(dāng)然,也有人認(rèn)為?;疃〞r器不合理,給出了不使用保活定時器的理由:
在出現(xiàn)短暫差錯的情況下,這可能會使一個非常好的連接釋放掉
耗費了不必要的帶寬
在按分組計費的情況下會在互聯(lián)網(wǎng)上花掉更多的錢
HTTP Keep-Alive
在HTTP早期 ,每個HTTP請求都要求打開一個TCP連接,并且使用一次之后就斷開這個TCP連接。
這種方式會帶來一些問題,尤其是包含圖片,JS,CSS的復(fù)雜網(wǎng)頁,一個完整的頁面需要很多個請求才能完成,如果每一個HTTP請求都需要新建并斷開一個TCP,這樣就會消耗很多服務(wù)器的TCP連接資源。
為了緩解這個問題,HTTP 1.1中出現(xiàn)了Keep-Alive這個特性,開啟HTTP Keep-Alive之后,能復(fù)用已有的TCP鏈接,當(dāng)前一個請求已經(jīng)響應(yīng)完畢,服務(wù)器端沒有立即關(guān)閉TCP鏈接,而是等待一段時間接收瀏覽器端可能發(fā)送過來的第二個請求,開啟Keep-Alive能節(jié)省的TCP建立和關(guān)閉的消耗。
下面看看我訪問一個網(wǎng)頁后,通過Wireshark抓取的數(shù)據(jù)包。
HTTP/1.1之后默認(rèn)開啟Keep-Alive, 在HTTP的頭域中增加Connection選項。當(dāng)設(shè)置為"Connection:keep-alive"表示開啟,設(shè)置為"Connection:close"表示關(guān)閉。
在上圖中,服務(wù)器經(jīng)過了大概2分鐘的時間,然后發(fā)出關(guān)閉TCP連接的請求。
現(xiàn)在,基本所有的應(yīng)用服務(wù)器都支持設(shè)置打開Keep-Alive,以及Keep-Alive timeout的設(shè)置。
總結(jié)
本文介紹了TCP中的4種定時器,并詳細(xì)的介紹了堅持定時器和?;疃〞r器。
在?;疃〞r器的介紹中,對比介紹了HTTP的Keep-Alive特性。HTTP協(xié)議的Keep-Alive意圖在于連接復(fù)用;TCP的keepalive機制在于?;?、心跳,檢測連接錯誤,兩者的作用完全不同。
因為TCP keepalive不能滿足實時性的要求,很多應(yīng)用程序會在應(yīng)用層實現(xiàn)heart beat(心跳包)來確認(rèn)TCP連接的可用性。