常見網絡編程面試題答案徵集與面試題(收集) ZZ 【網絡編程】

http://www.cnblogs.com/wickedboy237/archive/2013/05/12/3074362.htmlphp

1:tcp和udp的區別
2:流量控制和擁塞控制的實現機制
3:滑動窗口的實現機制
4:多線程如何同步。
5:進程間通信的方式有哪些,各有什麼優缺點
6:tcp鏈接創建的時候3次握手的具體過程,以及其中的每一步是爲何
7:tcp斷開鏈接的具體過程,其中每一步是爲何那麼作
8:tcp創建鏈接和斷開鏈接的各類過程當中的狀態轉換細節
9:epool與select的區別
10:epool中et和lt的區別與實現原理
11:寫一個server程序須要注意哪些問題
12:項目中遇到的難題,你是如何解決的html

 

1.tcp和udp的區別:linux

TCP與UDP區別web

TCP---傳輸控制協議,提供的是面向鏈接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間創建一個TCP鏈接,以後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另外一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,可是並不能保證它們能到達目的地。因爲UDP在傳輸數據報前不用在客戶和服務器之間創建一個鏈接,且沒有超時重發等機制,故而傳輸速度很快面試

Overview

TCP (Transmission Control Protocol) is the most commonly used protocol on the Internet. The reason for this is because TCP offers error correction. When the TCP protocol is used there is a "guaranteed delivery." This is due largely in part to a method called "flow control." Flow control determines when data needs to be re-sent, and stops the flow of data until previous packets are successfully transferred. This works because if a packet of data is sent, a collision may occur. When this happens, the client re-requests the packet from the server until the whole packet is complete and is identical to its original.

UDP (User Datagram Protocol) is anther commonly used protocol on the Internet. However, UDP is never used to send important data such as webpages, database information, etc; UDP is commonly used for streaming audio and video. Streaming media such as Windows Media audio files (.WMA) , Real Player (.RM), and others use UDP because it offers speed! The reason UDP is faster than TCP is because there is no form of flow control or error correction. The data sent over the Internet is affected by collisions, and errors will be present. Remember that UDP is only concerned with speed. This is the main reason why streaming media is not high quality.


算法



On the contrary, UDP has been implemented among some trojan horse viruses. Hackers develop scripts and trojans to run over UDP in order to mask their activities. UDP packets are also used in DoS (Denial of Service) attacks. It is important to know the difference between TCP port 80 and UDP port 80. If you don't know what ports are go here.

編程

Frame Structure

As data moves along a network, various attributes are added to the file to create a frame. This process is called encapsulation. There are different methods of encapsulation depending on which protocol and topology are being used. As a result, the frame structure of these packets differ as well. The images below show both the TCP and UDP frame structures.

緩存

TCP FRAME STRUCTURE

UDP FRAME STRUCTURE



The payload field contains the actually data. Notice that TCP has a more complex frame structure. This is largely due to the fact the TCP is a connection-oriented protocol. The extra fields are need to ensure the "guaranteed delivery" offered by TCP.安全

 

UDP 
    UDP 與 TCP 的主要區別在於 UDP 不必定提供可靠的數據傳輸。事實上,該協議不能保證數據準確無誤地到達目的地。UDP 在許多方面很是有效。當某個程序的目標是儘快地傳輸儘量多的信息時(其中任意給定數據的重要性相對較低),可以使用 UDP。ICQ 短消息使用 UDP 協議發送消息。 
    許多程序將使用單獨的TCP鏈接和單獨的UDP鏈接。重要的狀態信息隨可靠的TCP鏈接發送,而主數據流經過UDP發送。

TCP

    TCP的目的是提供可靠的數據傳輸,並在相互進行通訊的設備或服務之間保持一個虛擬鏈接。TCP在數據包接收無序、丟失或在交付期間被破壞時,負責數據恢復。它經過爲其發送的每一個數據包提供一個序號來完成此恢復。記住,較低的網絡層會將每一個數據包視爲一個獨立的單元,所以,數據包能夠沿徹底不一樣的路徑發送,即便它們都是同一消息的組成部分。這種路由與網絡層處理分段和從新組裝數據包的方式很是類似,只是級別更高而已。
    爲確保正確地接收數據,TCP要求在目標計算機成功收到數據時發回一個確認(即 ACK)。若是在某個時限內未收到相應的 ACK,將從新傳送數據包。若是網絡擁塞,這種從新傳送將致使發送的數據包重複。可是,接收計算機可以使用數據包的序號來肯定它是否爲重複數據包,並在必要時丟棄它。

TCP與UDP的選擇  

    若是比較UDP包和TCP包的結構,很明顯UDP包不具有TCP包複雜的可靠性與控制機制。與TCP協議相同,UDP的源端口數和目的端口數也都支持一臺主機上的多個應用。一個16位的UDP包包含了一個字節長的頭部和數據的長度,校驗碼域使其能夠進行總體校驗。(許多應用只支持UDP,如:多媒體數據流,不產生任何額外的數據,即便知道有破壞的包也不進行重發。)  
    很明顯,當數據傳輸的性能必須讓位於數據傳輸的完整性、可控制性和可靠性時,TCP協議是固然的選擇。當強調傳輸性能而不是傳輸的完整性時,如:音頻和多媒體應用,UDP是最好的選擇。在數據傳輸時間很短,以致於此前的鏈接過程成爲整個流量主體的狀況下,UDP也是一個好的選擇,如:DNS交換。把SNMP創建在UDP上的部分緣由是設計者認爲當發生網絡阻塞時,UDP較低的開銷使其有更好的機會去傳送管理數據。TCP豐富的功能有時會致使不可預料的性能低下,可是咱們相信在不遠的未來,TCP可靠的點對點鏈接將會用於絕大多數的網絡應用。服務器

 

2:流量控制和擁塞控制的實現機制

 

擁塞控制
網絡擁塞現象是指到達通訊子網中某一部分的分組數量過多,使得該部分網絡來不及處理,以至引發這部分乃至整個網絡性能降低的現象,嚴重時甚至會致使網絡通訊業務陷入停頓,即出現死鎖現象。擁塞控制是處理網絡擁塞現象的一種機制。
流量控制
數據的傳送與接收過程中極可能出現收方來不及接收的狀況,這時就須要對發方進行控制,以避免數據丟失。

流量控制機制:


  流量控制用於防止在端口阻塞的狀況下丟幀,這種方法是當發送或接收緩衝區開始溢出時經過將阻塞信號發送回源地址實現的。流量控制能夠有效的防止因爲網絡中瞬間的大量數據對網絡帶來的衝擊,保證用戶網絡高效而穩定的運行。

TCP的擁塞控制

1. 引言
TCP是Internet上通用的傳輸層協議之一,是目前應用最普遍的傳輸控制協議,其核心是擁塞控制機制。基於Internet的交換機的通訊信道、處理速度及緩衝存儲空間一般是網上全部主機共享的資源,也是網絡系統潛在的瓶頸。隨着信源主機數以及信源業務端業務量的不斷增多,瓶頸處就有可能發生資源競爭,從而致使網絡擁塞。TCP的一個重要組成部分是執行擁塞控制和擁塞恢復的算法集合。TCP擁塞控制算法的目標是最大限度利用網絡帶寬,同時不產生數據流傳輸中的擁塞現象。所以,自從上個世紀80年代出現第一次擁塞崩潰以來,TCP擁塞控制策略就在不斷地進行完善和改進。
2. 傳統的TCP擁塞控制機制
傳統的TCP中的擁塞控制機制主要是基於Van Jacobson提出的"慢啓動"算法、"擁塞避免"算法和一個用於估計週轉RTT(round trip time)的算法。
慢啓動算法經過觀察到新分組進入網絡的速率應該與另外一端返回確認的速率相同而進行工做。慢啓動爲發送方的TCP增長了另外一個窗口:擁塞窗口(congestion window), 記爲cwnd。當與另外一個網絡的主機創建TCP鏈接時,擁塞窗口被初始化爲1個報文段(即另外一端通告的報文段大小)。每收到一個ACK,擁塞窗口就增長一個報文段(cwnd以字節爲單位,可是慢啓動以報文段大小爲單位進行增長)。發送方取擁塞窗口與通告窗口中的最小值做爲發送上限。擁塞窗口是發送方使用的流量控制,而通告窗口則是接收方使用的流量控制。發送方開始時發送一個報文段,而後等待ACK。當收到該ACK時,擁塞窗口從1增長爲2,便可以發送兩個報文段。當收到這兩個報文段的ACK時,擁塞窗口就增長爲4,這是一種指數增長的關係。在某些點上可能達到了互聯網的容量,因而中間路由器開始丟棄分組。擁塞避免算法是一種處理丟失分組的方法。該算法假定因爲分組受到損壞引發的丟失是很是少的(遠小於1%),所以分組丟失就意味着在源主機和目的主機之間的某處網絡上發生了擁塞。有兩種分組丟失的指示:發生超時和接收到重複的確認。擁塞避免算法和慢啓動算法是兩個目的不一樣、獨立的算法。可是當擁塞發生時,咱們但願下降分組進入網絡的傳輸速率,因而能夠調用慢啓動來做到這一點。在實際中這兩個算法一般在一塊兒實現。1990年出現的TCP Reno版本增長了"快速重傳"算法、"快速恢復"算法,避免了當網絡擁塞不夠嚴重時採用"慢啓動"算法而形成過大地減少發送窗口尺寸的現象。
3. 擁塞控制的四個階段
a.慢啓動階段(slow start):發送方一開始便向網絡發送多個報文段,直至達到接收方通告的窗口大小爲止。當發送方和接收方處於同一個局域網時,這種方式是能夠的。可是若是在發送方和接收方之間存在多個路由器和速率較慢的鏈路時,就有可能出現一些問題。一些中間路由器必須緩存分組,並有可能耗盡存儲器的空間。
b.擁塞避免階段(congestion avoidance):當發現超時或收到3個相同ACK確認幀時,則表示有丟包事件,此時網絡已發生擁塞現象,此時要進行相應的擁塞控制。將慢啓動閾值設置爲當前擁塞窗口的一半;如檢測到超時,擁塞窗口就被置爲l。若是擁塞窗口小於或等於慢啓動閾值,TCP從新進人慢啓動階段;若是擁塞窗口大於慢啓動閾值,TCP執行擁塞避免算法。
c.快速重傳階段(fast retransmit):當TCP源端收到到三個相同的ACK副本時,即認爲有數據包丟失,則源端重傳丟失的數據包,而沒必要等待RTO超時。同時將ssthresh設置爲當前cwnd值的一半,而且將cwnd減爲原先的一半。
d.快速恢復階段(fast recovery) :當"舊"數據包離開網絡後,才能發送"新"數據包進入網絡,即同一時刻在網絡中傳輸的數據包數量是恆定的。若是發送方收到一個重複的ACK,則認爲已經有一個數據包離開了網絡,因而將擁塞窗口加1。

 


4. 對傳統TCP擁塞控制機制的發展及改進
4.1 對慢啓動的改進  
慢啓動(slow start)算法經過逐漸增長cwnd的大小來探測可用的網絡容量,防止鏈接開始時採用不合適的發送量致使網絡擁塞。然而有時該算法也會浪費可用的網絡容量,由於慢啓動算法老是從cwnd=l開始,每收到一個ACK,cwnd增長l,對RTT時間長的網絡,爲使cwnd達到一個合適的值,須要花很長的時間,特別是網絡實際容量很大時,會形成浪費。爲此可採用大的初始窗口,大的初始窗口避免了延遲ACK機制下單個報文段初始窗口的等待超時問題,縮短了小TCP流的傳輸時間和大延遲鏈路上的慢啓動時間。
在慢啓動階段,在每一個RTT時間內,cwnd增長一倍,這樣當cwnd增長到必定的值時,就可能致使以網絡可以處理的最大容量的2倍來發送數據,從而淹沒網絡。Hoe建議使用packet-pair算法和測量RTT來爲ssthresh估計合適值,以此來適時地結束慢啓動階段。可是因爲受各方面干擾,估算合理的ssthresh值並不容易,所以這個方法的效果是有限的。而Smooth-start較爲平滑地從慢啓動過渡到擁塞避免階段,減小了報文段丟失和突發通信量,提升了TCP擁塞控制的性能。
4.2 對重傳與恢復的改進
爲了不沒必要要的重傳超時,有人提出了一種受限傳輸機制:若是接收方的廣播窗口容許的話,發送方接收到一個或者兩個重複的ACK(acknowledgment)後,繼續傳輸新的數據報文段。受限的傳輸機制容許具備較小窗口的TCP鏈接進行錯誤恢復,並且避免了沒必要要的重傳。
有不少狀況下,數據報文段並無丟失,但TCP發送方可能會誤判數據報文段丟失,而後調用擁塞控制規程減小擁塞窗口的大小。好比當重傳定時器過早溢出時,發送方在重傳數據報文段時沒必要要地減小了擁塞窗口,而這時並無數據報文段丟失。若是是因爲數據報文段的從新組織而不是數據報文段丟失,而致使3個重複的確認,一樣會致使發送方沒必要要地在快速重傳數據報文段後減小擁塞窗口。
若是TCP的發送方在重傳數據報文段一個RTT後發現接收方接收到了重傳數據報文段的兩個拷貝,則能夠推斷重傳是沒必要要的。這時,TCP的發送方能夠撤銷對擁塞窗口的減小。發送方能夠經過將慢啓動門限增長到原始值,調用慢啓動規程使擁塞窗口恢復原先值。除了恢復擁塞窗口,TCP發送方還能夠調整重複確認門限或者重傳超時參數來避免因爲屢次沒必要要的重傳而浪費帶寬。
4.3 對公平性的改進 
在擁塞避免階段,若是沒有發生丟包事件,則TCP發送方的cwnd在每一個RTT時間內大約能夠增長一個報文段大小,但這樣會形成具備不一樣RTT時間或窗口尺寸的多個鏈接在瓶頸處對帶寬競爭的不公平性,RTT時間或窗口小的鏈接,相應的cwnd增加速度也相對緩慢,因此只能獲得很小一部分帶寬。
要解決上述問題,能夠經過在路由器處使用公平隊列和TCP友好緩存管理來進行控制以增長公平性。然而如沒有路由器的參與,要增長公平性,就要求TCP發送端的擁塞控制進行相應的改變,在擁塞避免階段使共享同一資源的各個TCP鏈接以相同速度發送數據,從而確保了各個鏈接間的公平性。
5. 結論
該文在研究和分析各類基於TCP的數據流擁塞控制算法和參考有關文檔的基礎上,對以TCP爲核心的擁塞控制機制進行了發展和改進,這些改進將使TCP的性能在不一樣的網絡中取得更好的性能。其中避免沒必要要的重傳超時、撤銷沒必要要的擁塞控制等是隨着網絡技術的發展而對TCP的改進,它們經過不一樣的方式改進了TCP的性能,具備更普遍的適應性。

 

 

TCP窗口和擁塞控制實現機制

TCP數據包格式

Source Port

Destination port

Sequence Number

Acknowledgement Number

Length

Reserved

Control Flags

Window Size

Check sum

Urgent Pointer

Options

DATA

       

注:校驗和是對全部數據包進行計算的。

TCP包的處理流程

接收:

ip_local_deliver

tcp_v4_rcv() (tcp_ipv4.c)→_tcp_v4_lookup()

tcp_v4_do_rcv(tcp_ipv4.c)→tcp_rcv_state_process  (OTHER STATES)

↓Established

tcp_rcv_established(tcp_in→tcp_ack_snd_check,(tcp_data_snd_check,tcp_ack (tcp_input.c)    

↓         ↓                  ↓                         ↓

tcp_data                       tcp_send_ack               tcp_write_xmit 

↓ (slow)  ↓(Fast)                         ↓

tcp_data_queue                             tcp_transmit_skb

↓         ↓                                   ↓

sk->data_ready (應用層)                              ip_queque_xmit

 

發送:

send

tcp_sendmsg

__tcp_push_pending_frames       tcp_write_timer

↓                            ↓

tcp_ retransmit_skb

tcp_transmit_skb     

ip_queue_xmit

TCP段的接收

_tcp_v4_lookup()用於在活動套接字的散列表中搜索套接字或SOCK結構體。

tcp_ack (tcp_input.c)用於處理確認包或具備有效ACK號的數據包的接受所涉及的全部任務:

調整接受窗口(tcp_ack_update_window())

刪除從新傳輸隊列中已確認的數據包(tcp_clean_rtx_queue())

對零窗口探測確認進行檢查

調整擁塞窗口(tcp_may_raise_cwnd())

從新傳輸數據包並更新從新傳輸計時器

tcp_event_data_recv()用於處理載荷接受所需的全部管理工做,包括最大段大小的更新,時間戳的更新,延遲計時器的更新。

 

tcp_data_snd_check(sk)檢查數據是否準備完畢並在傳輸隊列中等待,且它會啓動傳輸過程(若是滑動窗口機制的傳輸窗口和擁塞控制窗口容許的話),實際傳輸過程由tcp_write_xmit()啓動的。(這裏進行流量控制和擁塞控制)

 

tcp_ack_snd_check()用於檢查能夠發送確認的各類情形,一樣它會檢查確認的類型(它應該是快速的仍是應該被延遲的)。

 tcp_fast_parse_options(sk,th,tp)用於處理TCP數據包報頭中的Timestamp選項。

 

tcp_rcv_state_process()用於在TCP鏈接不處在ESTABLISHED狀態的時候處理輸入段。它主要用於處理該鏈接的狀態變換和管理工做。

 

Tcp_sequence()經過使用序列號來檢查所到達的數據包是不是無序的。若是它是一個無序的數據包,測激活QuickAck模式,以儘量快地將確認發送出去。

 

Tcp_reset()用來重置鏈接,且會釋放套接字緩衝區。

 

TCP段的發送

tcp_sendmsg()(TCP.C)用於將用戶地址空間中的載荷拷貝至內核中並開始以TCP段形式發送數據。在發送啓動前,它會檢查鏈接是否已經創建及它是否處於TCP_ESTABLISHED狀態,若是尚未創建鏈接,那麼系統調用會在wait_for_tcp_connect()一直等待直至有一個鏈接存在。

tcp_select_window() 用來進行窗口的選擇計算,以此進行流量控制。

 

tcp_send_skb()(tcp_output.c)用來將套接字緩衝區SKB添加到套接字傳輸隊列(sk->write_queue)並決定傳輸是否能夠啓動或者它必須在隊列中等待。它經過tcp_snd_test()例程來做出決定。若是結果是負的,那麼套接字緩衝區就仍留在傳輸隊列中; 若是傳輸成功的話,自動傳輸的計時器會在tcp_reset_xmit_timer()中自動啓動,若是某一特定時間後無該數據包的確認到達,那麼計時器就會啓動。(還沒有找到調用之處)(只有在發送FIN包時纔會調用此函數,其它狀況下都不會調用此函數2007,06,15)

 

tcp_snd_test()(tcp.h) 它用於檢查TCP段SKB是否能夠在調用時發送。

 

tcp_write_xmit()(tcp_output.c)它調用tcp_transmit_skb()函數來進行包的發送,它首先要查看此時TCP是否處於CLOSE狀態,若是不是此狀態則能夠發送包.進入循環,從SKB_BUF中取包,測試是否能夠發送包(),接下來查看是否要分片,分片完後調用tcp_transmit_skb()函數進行包的發送,直到發生擁塞則跳出循環,而後更新擁塞窗口.

 

tcp_transmits_skb()(TCP_OUTPUT.C)負責完備套接字緩衝區SKB中的TCP段以及隨後經過Internet協議將其發送。而且會在此函數中添加TCP報頭,最後調用tp->af_specific->queue_xmit)發送TCP包.

 

tcp_push_pending_frames()用於檢查是否存在傳輸準備完畢而在常規傳輸嘗試期間沒法發送的段。若是是這樣的狀況,則由tcp_write_xmit()啓動這些段的傳輸過程。

 

TCP實例的數據結構

Struct tcp_opt    sock.h

包含了用於TCP鏈接的TCP算法的全部變量。主要由如下部分算法或協議機制的變量組成:

序列和確認號

流控制信息

數據包往返時間

計時器

數據包頭中的TCP選項

自動和選擇性數據包重傳

TCP狀態機

tcp_rcv_state_process()主要處理狀態轉變和鏈接的管理工做,接到數據報的時候不一樣狀態會有不一樣的動做。

tcp_urg()負責對緊急數據進行處理

 

創建鏈接

tcp_v4_init_sock()(tcp_ipv4.c)用於運行各類初始化動做:初始化隊列和計時器,初始化慢速啓動和最大段大小的變量,設定恰當的狀態以及設定特定於PF_INET的例程的指針。

tcp_setsockopt()(tcp.c)該函數用於設定TCP協議的服務用戶所選定的選項:TCP_MAXSEG,TCP_NODELAY, TCP_CORK, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT, TCP_SYNCNT, TCP_LINGER2, TCP_DEFER_ACCEPT和TCP_WINDOW_CLAMP.

tcp_connect()(tcp_output.c)該函數用於初始化輸出鏈接:它爲sk_buff結構體中的數據單元報頭預留存儲空間,初始化滑動窗口變量,設定最大段長度,設定TCP報頭,設定合適的TCP狀態,爲從新初始化計時器和控制變量,最後,將一份初始化段的拷貝傳遞給tcp_transmit_skb()例程,以便進行鏈接創建段的從新傳輸發送並設定計時器。

 

 

從發送方和接收方角度看字節序列域

以下圖示:

 

snd_una

snd_nxt

snd_una+snd+wnd

rcv_wup

rcv_nxt

rcv_wup+rcv_wnd

數據以經確認

數據還沒有確認

剩餘傳輸窗口

右窗口邊界

數據以經確認

數據還沒有確認

剩餘傳輸窗口

 

流控制

流控制用於預防接受方TCP實例的接受緩衝區溢出。爲了實現流控制,TCP協議使用了滑動窗口機制。該機制以由接受方所進行的顯式傳輸資信分配爲基礎。

滑動窗口機制提供的三項重要做業:

分段併發送於若干數據包中的數據集初始順序能夠在接收方恢復。

能夠經過數據包的排序來識別丟失的或重複的數據包。

兩個TCP實例之間的數據流能夠經過傳輸資信賦值來加以控制。

Tcp_select_window()

 當發送一個TCP段用以指定傳輸資信大小的時候就會在tcp_transmit_skb()方法中調用tcp_select_window()。當前通告窗口的大小最初是由tcp_receive_window()指定的。隨後經過tcp_select_window()函數來查看該計算機中還有多少可用緩衝空間。這就夠成了哪一個提供給夥伴實例的新傳輸資信的決定基礎。

   一旦已經計算出新的通告窗口,就會將該資信(窗口信息)存儲在該鏈接的tcp_opt結構體(tp->rcv_wnd)中。一樣,tp->rcv_wup也會獲得調整;它會在計算和發送新的資信的時候存儲tp->rcv_nxt變量的當前值,這也就是一般所說的窗口更新。這是由於,在通告窗口已發送以後到達的每個數據包都必須沖銷其被授予的資信(窗口信息)。

另外,該方法中還使用另外一種TCP算法,即一般所說的窗口縮放算法。爲了可以使得傳輸和接收窗口的16位數字運做起來有意義,就必須引入該算法。基於這個緣由,咱們引入了一個縮放因子:它指定了用以將窗口大小移至左邊的比特數目,利用值Fin tp->rcv_wscale, 這就至關於因子爲2F 的通告窗口增長。

 

Tcp_receive_window()(tcp.c)用於計算在最後一段中所授予的傳輸資信還有多少剩餘,並將自那時起接收到的數據數量考慮進去。

Tcp_select_window()(tcp_output.c)用於檢查該鏈接有多少存儲空間能夠用於接收緩衝區以及能夠爲接收窗口選擇多大的尺寸。

Tcp_space()肯定可用緩衝區存儲空間有多大。在所肯定的緩衝區空間進行一些調整後,它最後檢查是否有足夠的緩衝區空間用於最大尺寸的TCP段。若是不是這樣,則意味着已經出現了癡呆窗口綜合症(SWS)。爲了不SWS的出現,在這種情形下所授予的資信就不會小於協議最大段。

 若是可用緩衝區空間大於最大段大小,則繼續計算新的接收窗口。該窗口變量被設定爲最後授予的那個資信的值(tcp->rcv_wnd)。若是舊的資信大於或小於多個段大小的數量,則窗口被設定爲空閒緩衝區空間最大段大小的下一個更小的倍數。以這種方式計算的窗口值是做爲新接收資信的參考而返回的。

 

Tcp_probe_timer()(tcp_timer.c)是零窗口探測計時器的處理例程。它首先檢查同位體在一段較長期間上是否曾提供了一個有意義的傳輸窗口。若是有若干個窗口探測發送失敗,則輸出一個錯誤消息以宣佈該TCP鏈接中存在嚴重並經過tcp_done()關閉該鏈接。主要是用來對方通告窗口爲0,則每隔定時間探測通告窗口是否有效.

若是還未達到該最大窗口探測數目,則由tcp_send_probe0()發送一個沒有載荷的TCP段,且確認段此後將授予一個大於NULL的資信。

 

Tcp_send_probe0()(tcp_output.c)利用tcp_write_wakeup(sk)來生成併發送零窗口探測數據包。若是不在須要探測計時器,或者當前仍舊存在途中數據包且它們的ACK不含有新的資信,那麼它就不會獲得從新啓動,且probes_out和backoff 參數會獲得重置。

不然,在已經發送一個探測數據包後這些參數會被增量,且零窗口探測計時器會獲得從新啓動,這樣它在某一時間後會再次到期。

 

Tcp_write_wakeup()(tcp_output.c)用於檢查傳輸窗口是否具備NULL大小以及SKB中數據段的開頭是否仍舊位於傳輸窗口範圍之內。到目前爲止所討論的零窗口問題的情形中,傳輸窗口具備NULL大小,因此tcp_xmit_probe_skb()會在else分支中獲得調用。它會生成所需的零窗口探測數據包。若是知足以上條件,且若是該傳輸隊列中至少存在一個傳輸窗口範圍之內的數據包,則咱們極可能就碰到了癡呆綜合症的狀況。和當前傳輸窗口的要求對應的數據包是由tcp_transmit_skb()生成併發送的。

Tcp_xmit_probe_skb(sk,urgent)(tcp_output.c)會建立一個沒有載荷的TCP段,由於傳輸窗口大小NULL,且當前不得傳輸數據。Alloc_skb()會取得一個具備長度MAX_TCP_HEADER的套接字緩衝區。如前所述,將無載荷發送,且數據包僅僅由該數據包報頭組成。

Tcp_ack_probe() (tcp_input.c) 當接收到一個ACK標記已設定的TCP段且若是懷疑它是對某一零窗口探測段的應答的時候,就會在tcp_ack()調用tcp_ack_probe()。根據該段是否開啓了接受窗口,由tcp_clear_xmit_timer()中止該零窗口探測計時器或者由tcp_reset_xmit_timer()從新起用該計時器。

擁塞檢測、迴避和處理

流控制確保了發送到接受方TCP實例的數據數量是該實例可以容納的,以免數據包在接收方端系統中丟失。然而,該轉發系統中可能會出現緩衝區益處——具體來講,是在Internet 協議的隊列中,當它們清空的速度不夠快且到達的數據多於可以經過網絡適配器獲得發送的數據量的時候,這種狀況稱爲擁塞。

TCP擁塞控制是經過控制一些重要參數的改變而實現的。TCP用於擁塞控制的參數主要有:

(1) 擁塞窗口(cwnd):擁塞控制的關鍵參數,它描述源端在擁塞控制狀況下一次最多能發送的數據包的數量。

(2) 通告窗口(awin):接收端給源端預設的發送窗口大小,它只在TCP鏈接創建的初始階段發揮做用。

(3) 發送窗口(win):源端每次實際發送數據的窗口大小。

(4) 慢啓動閾值(ssthresh):擁塞控制中慢啓動階段和擁塞避免階段的分界點。初始值一般設爲65535byte。

(5) 迴路響應時間(RTT):一個TCP數據包從源端發送到接收端,源端收到接收端確認的時間間隔。

(6) 超時重傳計數器(RTO):描述數據包從發送到失效的時間間隔,是判斷數據包丟失與否及網絡是否擁塞的重要參數。一般設爲2RTT或5RTT。

(7) 快速重傳閾值(tcprexmtthresh)::能觸發快速重傳的源端收到重複確認包ACK的個數。當此個數超過tcprexmtthresh時,網絡就進入快速重傳階段。tcprexmtthresh缺省值爲3。

四個階段

1.慢啓動階段

舊的TCP在啓動一個鏈接時會向網絡發送許多數據包,因爲一些路由器必須對數據包進行排隊,所以有可能耗盡存儲空間,從而致使TCP鏈接的吞吐量(throughput)急劇降低。避免這種狀況發生的算法就是慢啓動。當創建新的TCP鏈接時,擁塞窗口被初始化爲一個數據包大小(一個數據包缺省值爲536或512byte)。源端按cwnd大小發送數據,每收到一個ACK確認,cwnd就增長一個數據包發送量。顯然,cwnd的增加將隨RTT呈指數級(exponential)增加:1個、2個、4個、8個……。源端向網絡中發送的數據量將急劇增長。

2.擁塞避免階段

當發現超時或收到3個相同ACK確認幀時,網絡即發生擁塞(這一假定是基於由傳輸引發的數據包損壞和丟失的機率小於1%)。此時就進入擁塞避免階段。慢啓動閾值被設置爲當前cwnd的一半;超時時,cwnd被置爲1。若是cwnd≤ssthresh,則TCP從新進入慢啓動過程;若是cwnd>ssthresh,則TCP執行擁塞避免算法,cwnd在每次收到一個ACK時只增長1/cwnd個數據包(這裏將數據包大小segsize假定爲1)。

3.快速重傳和恢復階段

當數據包超時時,cwnd被設置爲1,從新進入慢啓動,這會致使過大地減少發送窗口尺寸,下降TCP鏈接的吞吐量。所以快速重傳和恢復就是在源端收到3個或3個以上重複ACK時,就判定數據包已經被丟失,並重傳數據包,同時將ssthresh設置爲當前cwnd的一半,而沒必要等到RTO超時。圖2和圖3反映了擁塞控制窗口隨時間在四個階段的變化狀況。

 

TCP用來檢測擁塞的機制的算法:

一、  超時。

一旦某個數據包已經發送,重傳計時器就會等待一個確認一段時間,若是沒有確認到達,就認爲某一擁塞致使了該數據包的丟失。對丟失的數據包的初始響應是慢速啓動階段,它從某個特定點起會被擁塞回避算法替代。

二、  重複確認

重複確認的接受表示某個數據段的丟失,由於,雖然隨後的段到達了,但基於累積ACK段的緣由它們卻不能獲得確認,。在這種情形下,一般認爲沒有出現嚴重的擁塞問題,由於隨後的段其實是接收到了。基於這個緣由,更爲新近的TCP版本對經由慢速啓動階段的丟失問題並不響應,而是對經由快速傳輸和快速恢復方法的丟失做出反應。

 

慢速啓動和擁塞回避

Tcp_cong_avoid() (tcp_input.c)用於在慢速啓動和擁塞回避算法中實現擁塞窗口增加。當某個具備有效確認ACK的輸入TCP段在tcp_ack()中進行處理時就會調用tcp_cong_avoid()

首先,會檢查該TCP鏈接是否仍舊處於慢速啓動階段,或者已經處於擁塞回避階段:

  在慢速啓動階段擁塞窗口會增長一個單位。可是,它不得超過上限值,這就意味着,在這一階段,伴隨每一輸入確認,擁塞窗口都會增長一個單位。在實踐中,這意味着能夠發送的數據量每次都會加倍。

 在擁塞回避階段,只有先前已經接收到N個確認的時候擁塞窗口才會增長一個單位,其中N等於當前擁塞窗口值。要實現這一行爲就須要引入補充變量tp->snd_cwnd_cnt;伴隨每一輸入確認,它都會增量一個單位。下一步,當達到擁塞窗口值tp->snd_cwnd的時候,tp->snd_cwnd就會最終增長一個單位,且tp->snd_cwnd_cnt獲得重置。經過這一方法就能完成線形增加。

總結:擁塞窗口最初有一個指數增加,可是,一旦達到閾值,就存在線形增加了。

 

Tcp_enter_loss(sk,how) (tcp_input.c)是在重傳計時器的處理例程中進行調用的,只有重傳超時期滿時所傳輸的數據段還未獲得確認,該計時器就會啓動。這裏假定該數據段或它的確認已丟失。在除了無線網絡的現代網絡中,數據包丟失僅僅出如今擁塞情形中,且爲了處理緩衝區溢出的問題將不得不在轉發系統中丟棄數據包。重傳定時器定時到時調用,用來計算CWND和閾值重傳丟失數據,而且進入慢啓動狀態.

 

Tcp_recal_ssthresh() (tcp.h)一旦檢測到了某個擁塞情形,就會在tcp_recalc_ssthresh(tp)中從新計算慢速啓動階段中指數增加的閾值。所以,一旦檢測到該擁塞,當前擁塞窗口tp->snd_cwnd的大小就會被減半,並做爲新的閾值被返回。該閾值不小於2。

 

快速重傳和快速恢復

TCP協議中集成可快速重傳算法以快速檢測出單個數據包的丟失。先前檢測數據包丟失的惟一依據是重傳計時器的到期,且TCP經過減小慢速啓動階段中的傳輸速率來響應這一問題。新的快速重傳算法使得TCP可以在重傳計時器到期以前檢測出數據包丟失,這也就是說,當一連串衆多段中某一段丟失的時候。接收方經過發送重複確認的方法來響應有一個段丟失的輸入數據段。

LINUX內核的TCP實例中兩個算法的協做方式:

▉  當接收到了三個確認複本時,變量tp->snd_ssthresh就會被設定爲當前傳辦理窗口的一半.丟失的段會獲得重傳,且擁塞窗口tp->snd_cwnd會取值tp->ssthresh+3*MSS,其中MSS表示最大段大小.

▉  每接收到一個重複確認,擁塞窗口tp->snd_cwnd 就會增長一個最大段大小的值,且會發送一個附加段(若是傳輸窗口大小容許的話)

▉  當新數據的第一個確認到達時,此時tp->snd_cwnd就會取tp->snd_ssthresh的原如值,它存儲在tp->prior_ssthresh中.這一確認應該對最初丟失的那個數據段進行確認.另外還應該確認全部在丟失數據包和第三個確認複本之間發送的段.

 

TCP中的計時器管理

Struct timer_list

{  struct timer_head list;

   unsigned long expires;

   unsigned long data;

   void (*function) (unsighed long);

   volatile   int running;

}

tcp_init_xmit_timers()(tcp_timer.c)用於初始化一組不一樣的計時器。Timer_list會被掛鉤進來,且函數指針被轉換到相應的行爲函數。

Tcp_clear_xmit_timer()(tcp.h)用於刪除timer_list結構體鏈表中某個鏈接的全部計時器組。

Tcp_reset_xmit_timer(sk,what,when)(tcp.h)用於設定在what到時間when中指定的計時器。

 

TCP數據發送流程具體流程應該是這樣的:

tcp_sendmsg()----->tcp_push_one()/tcp_push()----

                |                              |

                |                             \|/

                |--------------->__tcp_push_pending_frames()

                                               |

                                              \|/

                                         tcp_write_xmit()

                                               |

                                              \|/

                                         tcp_transmit_skb()



tcp_sendmsg()-->__tcp_push_pending_frames() --> tcp_write_xmit()-->tcp_transmit_skb()

 

write_queue 是發送隊列,包括已經發送的但還未確認的和還從未發送的,send_head指向其中的從未發送之第一個skb。 另外,仔細看了看tcp_sendmsg(),以爲它仍是但願來一個skb 就發送一個skb,即儘快發送的策略。 
有兩種狀況, 
一,mss > tp->max_window/2,則forced_push()總爲真,這樣, 每來一個skb,就調用__tcp_push_pending_frames(),爭取發送。 
二,mss < tp->max_window/2,所以一開始forced_push()爲假, 但因爲一開始send_head==NULL,所以會調用tcp_push_one(), 而它也是調用__tcp_push_pending_frames(),若是此時網絡狀況 良好,數據會順利發出,並很快確認,從而send_head又爲NULL,這樣下一次數據拷貝時,又能夠順利發出。這就是說, 在網絡狀況好時,tcp_sendmsg()會來一個skb就發送一個skb。 
只有當網絡狀況出現擁塞或延遲,send_head不能及時發出, 從而不能走tcp_push_one()這條線,纔會看數據的積累,此時, 每當數據積累到tp->max_window/2時,就嘗試push一下。 
而當拷貝數據的總量不多時,上述兩種狀況均可能不會知足, 
這樣,在循環結束時會調用一次tcp_push(),即每次用戶的完整 
一次發送會有一次push。

 

TCP包接收器(tcp_v4_rcv)將TCP包投遞到目的套接字進行接收處理. 當套接字正被用戶鎖定, TCP包將暫時排入該套接字的後備隊列(sk_add_backlog). 這時若是某一用戶線程企圖鎖定該套接字(lock_sock), 該線程被排入套接字的後備處理等待隊列(sk->lock.wq). 當用戶釋放上鎖的套接字時(release_sock), 後備隊列中的TCP包被當即注入TCP包處理器(tcp_v4_do_rcv)進行處理, 而後喚醒等待隊列中最早的一個用戶來得到其鎖定權. 若是套接字未被上鎖, 當用戶正在讀取該套接字時, TCP包將被排入套接字的預備隊列(tcp_prequeue), 將其傳遞到該用戶線程上下文中進行處理.

 

 

TCP定時器(TCP/IP詳解2)

TCP爲每條鏈接創建七個定時器:

一、  鏈接創建定時器在發送SYN報文段創建一條新鏈接時啓動。若是沒有在75秒內收到響 應,鏈接創建將停止。

當TCP實例將其狀態從LISTEN更改成SYN_RECV的時侯就會使用這一計時器.服務端的TCP實例最初會等待一個ACK三秒鐘.若是在這一段時間沒有ACK到達,則認爲該鏈接請求是過時的.

 

二、  重傳定時器在TCP發送數據時設定.若是定時器已超時而對端的確認還未到達,TCP將重傳數據.重傳定時器的值(即TCP等待對端確認的時間)是動態計算的,取決於TCP爲該 鏈接測量的往返時間和該報文段已重傳幾回.

 

三、  延遲ACK定時器在TCP收到必須被確認但無需立刻發出確認的數據時設定.TCP等 待時間200MS後發送確認響應.若是,在這200MS內,有數據要在該鏈接上發送,延遲的ACK響應就可隨着數據一塊兒發送回對端,稱爲稍帶確認.

 

 

四、  持續定時器在鏈接對端通告接收窗口爲0,阻止TCP繼續發送數據時設定.因爲鏈接對端發送的窗口通告不可靠,容許TCP繼續發送數據的後續窗口更新有可能丟失.所以,若是TCP有數據要發送,但對端通告接收窗口爲0,則持續定時器啓動,超時後向對端發送1字節的數據,斷定對端接收窗口是否已打開.與重傳定時器相似,持續定時器的值也是動態計算的,取決於鏈接的往返時間,在5秒到60秒之間取值.

 

五、  保活定時器在應用進程選取了插口的SO_KEEPALIVE選項時生效.若是鏈接的連續空閒時間超過2小時,保活定時器超時,向對端發送鏈接探測報文段,強迫對端響應.若是收到了期待的響應,TCP肯定對端主機工做正常,在該鏈接再次空閒超過2小時以前,TCP不會再進行保活測試,.若是收到的是其它響應,TCP肯定對端主要已重啓.若是連紐若干次保活測試都未收到響應,TCP就假定對端主機已崩潰,儘管它沒法區分是主機幫障仍是鏈接故障.

 

 

六、  FIN_WAIT-2定時器,當某個鏈接從FIN_WAIT-1狀態變遷到FIN_WAIN_2狀態,而且不能再接收任何數據時,FIN_WAIT_2定時器啓動,設爲10分鐘,定時器超時後,從新設爲75秒,第二次超時後鏈接被關閉,加入這個定時器的目的爲了不若是對端一直不發送FIN,某個鏈接會永遠滯留在FIN_WAIT_2狀態.

 

七、  TIME_WAIT定時器,通常也稱爲2MSL定時器.2MS指兩倍MSL.當鏈接轉移到TIME_WAIT狀態,即鏈接主動關閉時,定時器啓動.鏈接進入TIME_WAIT狀態時,定時器設定爲1分鐘,超時後,TCP控制塊和INTERNET PCB被刪除,端口號可從新使用.

TCP包含兩個定時器函數:一個函數每200MS調用一次(快速定時器);另外一個函數每500MS調用一次.延遲定時器與其它6個定時器有所不一樣;若是某個鏈接上設定了延遲ACK定時器,那麼下一次200MS定時器超時後,延遲的ACK必須被髮送.其它的定時器每500MS遞減一次,計數器減爲0時,就觸發相應的動做.

 3:滑動窗口的實現機制

 TCP的滑動窗口機制

 

TCP這個協議是網絡中使用的比較普遍,他是一個面向鏈接的可靠的傳輸協議。既然是一個可靠的傳輸協議就須要對數據進行確認。TCP協議裏窗口機制有2種一種是固定的窗口大小。一種是滑動的窗口。這個窗口大小就是咱們一次傳輸幾個數據。

 

咱們能夠看下面一張圖來分析一下固定窗口大小有什麼問題。

 

 

 

這裏咱們能夠看到假設窗口的大小是1,也是就每次只能發送一個數據只有接受方對這個數據進行確認了之後才能發送第2個數據。咱們能夠看到發送方每發送一個數據接受方就要給發送方一個ACK對這個數據進行確認。只有接受到了這個確認數據之後發送方纔能傳輸下個數據。

 

這樣咱們考慮一下若是說窗口太小,那麼當傳輸比較大的數據的時候須要不停的對數據進行確認,這個時候就會形成很大的延遲。若是說窗口的大小定義的過大。咱們假設發送方一次發送100個數據。可是接收方只能處理50個數據。這樣每次都會只對這50個數據進行確認。發送方下一次仍是發送100個數據,可是接受方仍是隻能處理50個數據。這樣就避免了沒必要要的數據來擁塞咱們的鏈路。因此咱們就引入了滑動窗口機制,窗口的大小並非固定的而是根據咱們之間的鏈路的帶寬的大小,這個時候鏈路是否擁護塞。接受方是否能處理這麼多數據了。

 

 

 

咱們看看滑動窗口是如何工做的。咱們看下面幾張圖。

 

 

 

首先是第一次發送數據這個時候的窗口大小是根據鏈路帶寬的大小來決定的。咱們假設這個時候窗口的大小是3。這個時候接受方收到數據之後會對數據進行確認告訴發送方我下次但願手到的是數據是多少。這裏咱們看到接收方發送的ACK=3。這個時候發送方收到這個數據之後就知道我第一次發送的3個數據對方只收到了2個。就知道第3個數據對方沒有收到。下次在發送的時候就從第3個數據開始發。這個時候窗口大小就變成了2 。

 

 

 

這個時候發送方發送2個數據。

 

 

 

看到接收方發送的ACK是5就表示他下一次但願收到的數據是5,發送方就知道我剛纔發送的2個數據對方收了這個時候開始發送第5個數據。

 

這就是滑動窗口的工做機制,當鏈路變好了或者變差了這個窗口還會發生變話,並非第一次協商好了之後就永遠不變了。

 

 

 

滑動窗口協議

 

滑動窗口協議,是TCP使用的一種流量控制方法。該協議容許發送方在中止並等待確認前能夠連續發送多個分組。因爲發送方沒必要每發一個分組就停下來等待確認,所以該協議能夠加速數據的傳輸。

 

只有在接收窗口向前滑動時(與此同時也發送了確認),發送窗口才有可能向前滑動。  

 

收發兩端的窗口按照以上規律不斷地向前滑動,所以這種協議又稱爲滑動窗口協議。  

 

當發送窗口和接收窗口的大小都等於 1時,就是中止等待協議。  

 

當發送窗口大於1,接收窗口等於1時,就是回退N步協議。  

 

當發送窗口和接收窗口的大小均大於1時,就是選擇重發協議。  

 

協議中規定,對於窗口內未經確認的分組須要重傳。這種分組的數量最多能夠等於發送窗口的大小,即滑動窗口的大小n減去1(由於發送窗口不可能大於(n-1),起碼接收窗口要大於等於1)。

 

工做原理

 

  TCP協議在工做時,若是發送端的TCP協議軟件每傳輸一個數據分組後,必須等待接收端的確認纔可以發送下一個分組,因爲網絡傳輸的時延,將有大量時間被用於等待確認,致使傳輸效率低下。爲此TCP在進行數據傳輸時使用了滑動窗口機制。  

 

TCP滑動窗口用來暫存兩臺計算機問要傳送的數據分組。每臺運行TCP協議的計算機有兩個滑動窗口:一個用於數據發送,另外一個用於數據接收。發送端待發數據分組在緩衝區排隊等待送出。被滑動窗口框入的分組,是能夠在未收到接收確認的狀況下最多送出的部分。滑動窗口左端標誌X的分組,是已經被接收端確認收到的分組。隨着新的確認到來,窗口不斷向右滑動。  

 

TCP協議軟件依靠滑動窗口機制解決傳輸效率和流量控制問題。它能夠在收到確認信息以前發送多個數據分組。這種機制使得網絡通訊處於忙碌狀態,提升了整個網絡的吞吐率,它還解決了端到端的通訊流量控制問題,容許接收端在擁有容納足夠數據的緩衝以前對傳輸進行限制。在實際運行中,TCP滑動窗口的大小是能夠隨時調整的。收發端TCP協議軟件在進行分組確認通訊時,還交換滑動窗口控制信息,使得雙方滑動窗口大小能夠根據須要動態變化,達到在提升數據傳輸效率的同時,防止擁塞的發生。 稱窗口左邊沿向右邊沿靠近爲窗口合攏,這種現象發生在數據被髮送和確認時。  

 

當窗口右邊沿向右移動時將容許發送更多的數據,稱之爲窗口張開。這種現象發生在另外一端的接收進程讀取已經確認的數據並釋放了TCP的接收緩存時。 當右邊沿向左移動時,稱爲窗口收縮。Host Requirements RFC強烈建議不要使用這種方式。但TCP必須可以在某一端產生這種狀況時進行處理。  

 

若是左邊沿到達右邊沿,則稱其爲一個零窗口。

 

注意事項

 

  (1)發送方沒必要發送一個全窗口大小的數據。  (2)來自接收方的一個報文段確認數據並把窗口向右邊滑動,這是由於窗口的大小事相對於確認序號的。  (3)窗口的大小能夠減少,可是窗口的右邊沿卻不可以向左移動。  (4)接收方在發送一個ACK前沒必要等待窗口被填滿。

 

 

 

 

 

 

 

 

 

滑動窗口     

 

滑動窗口(Sliding window )是一種流量控制技術。早期的網絡通訊中,通訊雙方不會考慮網絡的擁擠狀況直接發送數據。因爲你們不知道網絡擁塞情況,一塊兒發送數據,致使中間結點阻塞掉包,誰也發不了數據。因此就有了滑動窗口機制來解決此問題。參見滑動窗口如何根據網絡擁塞發送數據仿真視頻。圖片是一個滑動窗口的實例:  

 

滑動窗口協議是用來改善吞吐量的一種技術,即允許發送方在接收任何應答以前傳送附加的包。接收方告訴發送方在某一時刻能送多少包(稱窗口尺寸)。 

 

TCP中採用滑動窗口來進行傳輸控制,滑動窗口的大小意味着接收方還有多大的緩衝區能夠用於接收數據。發送方能夠經過滑動窗口的大小來肯定應該發送多少字節的數據。當滑動窗口爲0時,發送方通常不能再發送數據報,但有兩種狀況除外,一種狀況是能夠發送緊急數據,例如,容許用戶終止在遠端機上的運行進程。另外一種狀況是發送方能夠發送一個1字節的數據報來通知接收方從新聲明它但願接收的下一字節及發送方的滑動窗口大小。  

 

(1).窗口機制  

 

滑動窗口協議的基本原理就是在任意時刻,發送方都維持了一個連續的容許發送的幀的序號,稱爲發送窗口;同時,接收方也維持了一個連續的容許接收的幀的序號,稱爲接收窗口。發送窗口和接收窗口的序號的上下界不必定要同樣,甚至大小也能夠不一樣。不一樣的滑動窗口協議窗口大小通常不一樣。發送方窗口內的序列號表明了那些已經被髮送,可是尚未被確認的幀,或者是那些能夠被髮送的幀。下面舉一個例子(假設發送窗口尺寸爲2,接收窗口尺寸爲1):  分析:①初始態,發送方沒有幀發出,發送窗口先後沿相重合。接收方0號窗口打開,等待接收0號幀;②發送方打開0號窗口,表示已發出0幀但尚確認返回信息。此時接收窗口狀態不變;③發送方打開0、1號窗口,表示0、1號幀均在等待確認之列。至此,發送方打開的窗口數已達規定限度,在未收到新的確認返回幀以前,發送方將暫停發送新的數據幀。接收窗口此時狀態仍未變;④接收方已收到0號幀,0號窗口關閉,1號窗口打開,表示準備接收1號幀。此時發送窗口狀態不變;⑤發送方收到接收方發來的0號幀確認返回信息,關閉0號窗口,表示從重發表中刪除0號幀。此時接收窗口狀態仍不變;⑥發送方繼續發送2號幀,2號窗口打開,表示2號幀也歸入待確認之列。至此,發送方打開的窗口又已達規定限度,在未收到新的確認返回幀以前,發送方將暫停發送新的數據幀,此時接收窗口狀態仍不變;⑦接收方已收到1號幀,1號窗口關閉,2號窗口打開,表示準備接收2號幀。此時發送窗口狀態不變;⑧發送方收到接收方發來的1號幀收畢的確認信息,關閉1號窗口,表示從重發表中刪除1號幀。此時接收窗口狀態仍不變。  若從滑動窗口的觀點來統一看待1比特滑動窗口、後退n及選擇重傳三種協議,它們的差異僅在於各自窗口尺寸的大小不一樣而已。1比特滑動窗口協議:發送窗口=1,接收窗口=1;後退n協議:發送窗口>1,接收窗口=1;選擇重傳協議:發送窗口>1,接收窗口>1。  (2).1比特滑動窗口協議  當發送窗口和接收窗口的大小固定爲1時,滑動窗口協議退化爲停等協議(stop-and-wait)。該協議規定發送方每發送一幀後就要停下來,等待接收方已正確接收的確認(acknowledgement)返回後才能繼續發送下一幀。因爲接收方須要判斷接收到的幀是新發的幀仍是從新發送的幀,所以發送方要爲每個幀加一個序號。因爲停等協議規定只有一幀徹底發送成功後才能發送新的幀,於是只用一比特來編號就夠了。其發送方和接收方運行的流程圖如圖所示。  (3).後退n協議  因爲停等協議要爲每個幀進行確認後才繼續發送下一幀,大大下降了信道利用率,所以又提出了後退n協議。後退n協議中,發送方在發完一個數據幀後,不停下來等待應答幀,而是連續發送若干個數據幀,即便在連續發送過程當中收到了接收方發來的應答幀,也能夠繼續發送。且發送方在每發送完一個數據幀時都要設置超時定時器。只要在所設置的超時時間內仍收到確認幀,就要重發相應的數據幀。如:當發送方發送了N個幀後,若發現該N幀的前一個幀在計時器超時後仍未返回其確認信息,則該幀被判爲出錯或丟失,此時發送方就不得不從新發送出錯幀及其後的N幀。  從這裏不難看出,後退n協議一方面因連續發送數據幀而提升了效率,但另外一方面,在重傳時又必須把原來已正確傳送過的數據幀進行重傳(僅因這些數據幀以前有一個數據幀出了錯),這種作法又使傳送效率下降。因而可知,若傳輸信道的傳輸質量不好於是誤碼率較大時,連續測協議不必定優於中止等待協議。此協議中的發送窗口的大小爲k,接收窗口還是1。  (4).選擇重傳協議  在後退n協議中,接收方若發現錯誤幀就再也不接收後續的幀,即便是正確到達的幀,這顯然是一種浪費。另外一種效率更高的策略是當接收方發現某幀出錯後,其後繼續送來的正確的幀雖然不能當即遞交給接收方的高層,但接收方仍可收下來,存放在一個緩衝區中,同時要求發送方從新傳送出錯的那一幀。一旦收到從新傳來的幀後,就能夠原已存於緩衝區中的其他幀一併按正確的順序遞交高層。這種方法稱爲選擇重發(SELECTICE REPEAT),其工做過程如圖所示。顯然,選擇重發減小了浪費,但要求接收方有足夠大的緩衝區空間。  滑動窗口功能:確認、差錯控制、流量控制。

 

流量控制

 

  TCP的特色之一是提供體積可變的滑動窗口機制,支持端到端的流量控制。TCP的窗口以字節爲單位進行調整,以適應接收方的處理能力。處理過程以下:  減少窗口尺寸

 

(1)TCP鏈接階段,雙方協商窗口尺寸,同時接收方預留數據緩存區;  (2)發送方根據協商的結果,發送符合窗口尺寸的數據字節流,並等待對方的確認;  (3)發送方根據確認信息,改變窗口的尺寸,增長或者減小發送未獲得確認的字節流中的字節數。調整過程包括:若是出現發送擁塞,發送窗口縮小爲原來的一半,同時將超時重傳的時間間隔擴大一倍。  滑動窗口機制爲端到端設備間的數據傳輸提供了可靠的流量控制機制。然而,它只能在源端設備和目的端設備起做用,當網絡中間設備(例如路由器等)發生擁塞時,滑動窗口機制將不起做用。

4:多線程如何同步:

 在這裏簡單說一下linux多線程同步的方法吧(win上有必定的差異,也有必定的累似)

1:線程數據,每一個線程數據建立一個鍵,它和這個鍵相關聯,在各個線程裏,都使用這個鍵來指代線程數據,但在不一樣的線程裏,這個鍵表明的數據是不一樣的,在同一個線程裏,它表明一樣的數據內容。以此來達到線程安全的目的。
2:互斥鎖,就是在各個線程要使用的一些公共數據以前加鎖,使用以後釋放鎖,這個是很是經常使用的線程安全控制的方法,而頻繁的加解鎖也對效率有必定的影響。
3:條件變量,而條件變量經過容許線程阻塞和等待另外一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一塊兒使用。使用時,條件變量被用來阻塞一個線程,當條件不知足時,線程每每解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將從新鎖定互斥鎖並從新測試條件是否知足。通常說來,條件變量被用來進行線程間的同步。
4:信號量,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增長時,調用函數sem_post()增長信號量。只有當信號量值大於0時,才能使用公共資源,使用後,函數sem_wait()減小信號量。函數sem_trywait()和函數pthread_ mutex_trylock()起一樣的做用,它是函數sem_wait()的非阻塞版本

另外pthread_join也能夠等待一個線程的終止。可能還有一些其餘我暫時沒有想到的方法,歡迎補充

5:進程間通信的方式有哪些,各有什麼優缺點

進程間通訊主要包括管道, 系統IPC(包括消息隊列,信號量,共享存儲), socket.

管道包括三種:1)普通管道PIPE, 一般有種限制,一是半雙工,只能單向傳輸;二是隻能在父子進程間使用. 2)流管道s_pipe: 去除了第一種限制,能夠雙向傳輸. 3)命名管道:name_pipe, 去除了第二種限制,能夠在許多並不相關的進程之間進行通信.

系統IPC的三種方式類同,都是使用了內核裏的標識符來識別

管道: 優勢是全部的UNIX實現都支持, 而且在最後一個訪問管道的進程終止後,管道就被徹底刪除;缺陷是管道只容許單向傳輸或者用於父子進程之間

系統IPC: 優勢是功能強大,能在絕不相關進程之間進行通信; 缺陷是關鍵字KEY_T使用了內核標識,佔用了內核資源,並且只能被顯式刪除,並且不能使用SOCKET的一些機制,例如select,epoll等.

socket能夠跨網絡通信,其餘進程間通信的方式都不能夠,只能是本機進程通信。

6:tcp鏈接創建的時候3次握手的具體過程,以及其中的每一步是爲何

創建鏈接採用的3次握手協議,具體是指:

第一次握手是客戶端connect鏈接到server,server accept client的請求以後,向client端發送一個消息,至關於說我都準備好了,你鏈接上我了,這是第二次握手,第3次握手就是client向server發送的,就是對第二次握手消息的確認。以後client和server就開始通信了。

 


7:tcp斷開鏈接的具體過程,其中每一步是爲何那麼作

斷開鏈接的4次握手,具體以下:

斷開鏈接的一端發送close請求是第一次握手,另一端接收到斷開鏈接的請求以後須要對close進行確認,發送一個消息,這是第二次握手,發送了確認消息以後還要向對端發送close消息,要關閉對對端的鏈接,這是第3次握手,而在最初發送斷開鏈接的一端接收到消息以後,進入到一個很重要的狀態time_wait狀態,這個狀態也是面試官常常問道的問題,最後一次握手是最初發送斷開鏈接的一端接收到消息以後。對消息的確認。

 


8:tcp創建鏈接和斷開鏈接的各類過程當中的狀態轉換細節

2.6 TCP鏈接的創建和終止

爲幫助你們理解connect、accept和close這3個函數並使用netstat程序調試TCP應用,咱們必須瞭解TCP鏈接如何創建和終止,並掌握TCP的狀態轉換圖。

2.6.1 三路握手

創建一個TCP鏈接時會發生下述情形。

(1) 服務器必須準備好接受外來的鏈接。這一般經過調用socket、bind和listen這3個函數來完成,咱們稱之爲被動打開(passive open)。

(2) 客戶經過調用connect發起主動打開(active open)。這致使客戶TCP發送一個SYN(同步)分節,它告訴服務器客戶將在(待創建的)鏈接中發送的數據的初始序列號。一般SYN分節不攜帶數據,其所在IP數據報只含有一個IP首部、一個TCP首部及可能有的TCP選項(咱們稍後講解)。

(3) 服務器必須確認(ACK)客戶的SYN,同時本身也得發送一個SYN分節,它含有服務器將在同一鏈接中發送的數據的初始序列號。服務器在單個分節中發送SYN和對客戶SYN的ACK(確認)。

(4) 客戶必須確認服務器的SYN。

這種交換至少須要3個分組,所以稱之爲TCP的三路握手(three-way handshake)。圖2-2展現了所交換的3個分節。

 

 

圖2-2給出的客戶的初始序列號爲J,服務器的初始序列號爲K。ACK中的確認號是發送這個ACK的一端所期待的下一個序列號。由於SYN佔據一個字節的序列號空間,因此每個SYN的ACK中的確認號就是該SYN的初始序列號加1。相似地,每個FIN(表示結束)的ACK中的確認號爲該FIN的序列號加1。

創建TCP鏈接就比如一個電話系統[Nemeth 1997]。socket函數等同於有電話可用。bind函數是在告訴別人你的電話號碼,這樣他們能夠呼叫你。listen函數是打開電話振鈴,這樣當有一個外來呼叫到達時,你就能夠聽到。connect函數要求咱們知道對方的電話號碼並撥打它。accept函數發生在被呼叫的人應答電話之時。由accept返回客戶的標識(即客戶的IP地址和端口號)相似於讓電話機的呼叫者ID功能部件顯示呼叫者的電話號碼。然而二者的不一樣之處在於accept只在鏈接創建以後返回客戶的標識,而呼叫者ID功能部件卻在咱們選擇應答或不該答電話以前顯示呼叫者的電話號碼。若是使用域名系統DNS(見第11章),它就提供了一種相似於電話簿的服務。getaddrinfo相似於在電話簿中查找某我的的電話號碼,getnameinfo則相似於有一本按照電話號碼而不是按照用戶名排序的電話簿。

2.6.2 TCP選項

每個SYN能夠含有多個TCP選項。下面是經常使用的TCP選項。

MSS選項。發送SYN的TCP一端使用本選項通告對端它的最大分節大小(maximum segment size)即MSS,也就是它在本鏈接的每一個TCP分節中願意接受的最大數據量。發送端TCP使用接收端的MSS值做爲所發送分節的最大大小。咱們將在7.9節看到如何使用TCP_MAXSEG套接字選項提取和設置這個TCP選項。

窗口規模選項。TCP鏈接任何一端可以通告對端的最大窗口大小是65535,由於在TCP首部中相應的字段佔16位。然而當今因特網上業已普及的高速網絡鏈接(45 Mbit/s或更快,如RFC 1323[Jacobson, Braden, and Borman 1992]所述)或長延遲路徑(衛星鏈路)要求有更大的窗口以得到儘量大的吞吐量。這個新選項指定TCP首部中的通告窗口必須擴大(即左移)的位數(0~14),所以所提供的最大窗口接近1 GB(65535×214)。在一個TCP鏈接上使用窗口規模的前提是它的兩個端系統必須都支持這個選項。咱們將在7.5節看到如何使用SO_RCVBUF套接字選項影響這個TCP選項。

爲提供與不支持這個選項的較早實現間的互操做性,需應用以下規則。TCP能夠做爲主動打開的部份內容隨它的SYN發送該選項,可是隻在對端也隨它的SYN發送該選項的前提下,它才能擴大本身窗口的規模。相似地,服務器的TCP只有接收到隨客戶的SYN到達的該選項時,才能發送該選項。本邏輯假定實現忽略它們不理解的選項,如此忽略是必需的要求,也已廣泛知足,但沒法保證全部實現都知足此要求。

時間戳選項。這個選項對於高速網絡鏈接是必要的,它能夠防止由失而復現的分組 可能形成的數據損壞。它是一個較新的選項,也以相似於窗口規模選項的方式協商處理。做爲網絡編程人員,咱們無需考慮這個選項。

TCP的大多數實現都支持這些經常使用選項。後兩個選項有時稱爲"RFC 1323選項",由於它們是在RFC 1323[Jacobson, Braden, and Borman 1992]中說明的。既然高帶寬或長延遲的網絡被稱爲"長胖管道"(long fat pipe),這兩個選項也稱爲"長胖管道選項"。TCPv1的第24章對這些選項有詳細的敘述。

2.6.3 TCP鏈接終止

TCP創建一個鏈接需3個分節,終止一個鏈接則需4個分節。

(1) 某個應用進程首先調用close,咱們稱該端執行主動關閉(active close)。該端的TCP因而發送一個FIN分節,表示數據發送完畢。

(2) 接收到這個FIN的對端執行被動關閉(passive close)。這個FIN由TCP確認。它的接收也做爲一個文件結束符(end-of-file)傳遞給接收端應用進程(放在已排隊等候該應用進程接收的任何其餘數據以後),由於FIN的接收意味着接收端應用進程在相應鏈接上再無額外數據可接收。

(3) 一段時間後,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這致使它的TCP也發送一個FIN。

(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。

既然每一個方向都須要一個FIN和一個ACK,所以一般須要4個分節。咱們使用限定詞"一般"是由於:某些情形下步驟1的FIN隨數據一塊兒發送;另外,步驟2和步驟3發送的分節都出自執行被動關閉那一端,有可能被合併成一個分節。圖2-3展現了這些分組。

 

 

相似SYN,一個FIN也佔據1個字節的序列號空間。所以,每一個FIN的ACK確認號就是這個FIN的序列號加1。

在步驟2與步驟3之間,從執行被動關閉一端到執行主動關閉一端流動數據是可能的。這稱爲半關閉(half-close),咱們將在6.6節隨shutdown函數再詳細介紹。

當套接字被關閉時,其所在端TCP各自發送了一個FIN。咱們在圖中指出,這是由應用進程調用close而發生的,不過需認識到,當一個Unix進程不管自願地(調用exit或從main函數返回)仍是非自願地(收到一個終止本進程的信號)終止時,全部打開的描述符都被關閉,這也致使仍然打開的任何TCP鏈接上也發出一個FIN。

圖2-3展現了客戶執行主動關閉的情形,不過咱們指出,不管是客戶仍是服務器,任何一端均可以執行主動關閉。一般狀況是客戶執行主動關閉,可是某些協議(譬如值得注意的HTTP/1.0)卻由服務器執行主動關閉。

 

2.6.4 TCP狀態轉換圖

TCP涉及鏈接創建和鏈接終止的操做能夠用狀態轉換圖(state transition diagram)來講明,如圖2-4所示。

TCP爲一個鏈接定義了11種狀態,而且TCP規則規定如何基於當前狀態及在該狀態下所接收的分節從一個狀態轉換到另外一個狀態。舉例來講,當某個應用進程在CLOSED狀態下執行主動打開時,TCP將發送一個SYN,且新的狀態是SYN_SENT。若是這個TCP接着接收到一個帶ACK的SYN,它將發送一個ACK,且新的狀態是ESTABLISHED。這個最終狀態是絕大多數數據傳送發生的狀態。

自ESTABLISHED狀態引出的兩個箭頭處理鏈接的終止。若是某個應用進程在接收到一個FIN以前調用close(主動關閉),那就轉換到FIN_WAIT_1狀態。但若是某個應用進程在ESTABLISHED狀態期間接收到一個FIN(被動關閉),那就轉換到CLOSE_WAIT狀態。

咱們用粗實線表示一般的客戶狀態轉換,用粗虛線表示一般的服務器狀態轉換。圖中還註明存在兩個咱們不曾討論的轉換:一個爲同時打開(simultaneous open),發生在兩端幾乎同時發送SYN而且這兩個SYN在網絡中交錯的情形下,另外一個爲同時關閉(simultaneous close),發生在兩端幾乎同時發送FIN的情形下。TCPv1的第18章中有這兩種狀況的例子和討論,它們是可能發生的,不過很是罕見。

展現狀態轉換圖的緣由之一是給出11種TCP狀態的名稱。這些狀態可以使用netstat顯示,它是一個在調試客戶/服務器應用時頗有用的工具。咱們將在第5章中使用netstat去監視狀態的變化。

 

 

2.6.5 觀察分組

圖2-5展現一個完整的TCP鏈接所發生的實際分組交換狀況,包括鏈接創建、數據傳送和鏈接終止3個階段。圖中還展現了每一個端點所歷經的TCP狀態。

本例中的客戶通告一個值爲536的MSS(代表該客戶只實現了最小重組緩衝區大小),服務器通告一個值爲1460的MSS(以太網上IPv4的典型值)。不一樣方向上MSS值不相同不成問題(見習題2.5)。

 

 

一旦創建一個鏈接,客戶就構造一個請求併發送給服務器。這裏咱們假設該請求適合於單個TCP分節(即請求大小小於服務器通告的值爲1460字節的MSS)。服務器處理該請求併發送一個應答,咱們假設該應答也適合於單個分節(本例即小於536字節)。圖中使用粗箭頭表示這兩個數據分節。注意,服務器對客戶請求的確認是伴隨其應答發送的。這種作法稱爲捎帶(piggybacking),它一般在服務器處理請求併產生應答的時間少於200 ms時發生。若是服務器耗用更長時間,譬如說1 s,那麼咱們將看到先是確認後是應答。(TCP數據流機理在TCPv1的第19章和第20章中詳細敘述。)

圖中隨後展現的是終止鏈接的4個分節。注意,執行主動關閉的那一端(本例子中爲客戶)進入咱們將在下一節中討論的TIME_WAIT狀態。

圖2-5中值得注意的是,若是該鏈接的整個目的僅僅是發送一個單分節的請求和接收一個單分節的應答,那麼使用TCP有8個分節的開銷。若是改用UDP,那麼只需交換兩個分組:一個承載請求,一個承載應答。然而從TCP切換到UDP將喪失TCP提供給應用進程的所有可靠性,迫使可靠服務的一大堆細節從傳輸層(TCP)轉移到UDP應用進程。TCP提供的另外一個重要特性即擁塞控制也必須由UDP應用進程來處理。儘管如此,咱們仍然須要知道許多網絡應用是使用UDP構建的,由於它們須要交換的數據量較少,而UDP避免了TCP鏈接創建和終止所需的開銷。

2.7 TIME_WAIT狀態

毫無疑問,TCP中有關網絡編程最不容易理解的是它的TIME_WAIT狀態。在圖2-4中咱們看到執行主動關閉的那端經歷了這個狀態。該端點停留在這個狀態的持續時間是最長分節生命期(maximum segment lifetime,MSL)的兩倍,有時候稱之爲2MSL。

任何TCP實現都必須爲MSL選擇一個值。RFC 1122[Braden 1989]的建議值是2分鐘,不過源自Berkeley的實現傳統上改用30秒這個值。這意味着TIME_WAIT狀態的持續時間在1分鐘到4分鐘之間。MSL是任何IP數據報可以在因特網中存活的最長時間。咱們知道這個時間是有限的,由於每一個數據報含有一個稱爲跳限(hop limit)的8位字段(見圖A-1中IPv4的TTL字段和圖A-2中IPv6的跳限字段),它的最大值爲255。儘管這是一個跳數限制而不是真正的時間限制,咱們仍然假設:具備最大跳限(255)的分組在網絡中存在的時間不可能超過MSL秒。

分組在網絡中"迷途"一般是路由異常的結果。某個路由器崩潰或某兩個路由器之間的某個鏈路斷開時,路由協議需花數秒鐘到數分鐘的時間才能穩定並找出另外一條通路。在這段時間內有可能發生路由循環(路由器A把分組發送給路由器B,而B再把它們發送回A),咱們關心的分組可能就此陷入這樣的循環。假設迷途的分組是一個TCP分節,在它迷途期間,發送端TCP超時並重傳該分組,而重傳的分組卻經過某條候選路徑到達最終目的地。然而不久後(自迷途的分組開始其旅程起最多MSL秒之內)路由循環修復,早先迷失在這個循環中的分組最終也被送到目的地。這個原來的分組稱爲迷途的重複分組(lost duplicate)或漫遊的重複分組(wandering duplicate)。TCP必須正確處理這些重複的分組。

TIME_WAIT狀態有兩個存在的理由:

(1) 可靠地實現TCP全雙工鏈接的終止;

(2) 容許老的重複分節在網絡中消逝。

第一個理由能夠經過查看圖2-5並假設最終的ACK丟失了來解釋。服務器將從新發送它的最終那個FIN,所以客戶必須維護狀態信息,以容許它從新發送最終那個ACK。要是客戶不維護狀態信息,它將響應以一個RST(另一種類型的TCP分節),該分節將被服務器解釋成一個錯誤。若是TCP打算執行全部必要的工做以完全終止某個鏈接上兩個方向的數據流(即全雙工關閉),那麼它必須正確處理鏈接終止序列4個分節中任何一個分節丟失的狀況。本例子也說明了爲何執行主動關閉的那一端是處於TIME_WAIT狀態的那一端:由於可能不得不重傳最終那個ACK的就是那一端。

爲理解存在TIME_WAIT狀態的第二個理由,咱們假設在12.106.32.254的1500端口和206.168.112.219的21端口之間有一個TCP鏈接。咱們關閉這個鏈接,過一段時間後在相同的IP地址和端口之間創建另外一個鏈接。後一個鏈接稱爲前一個鏈接的化身(incarnation),由於它們的IP地址和端口號都相同。TCP必須防止來自某個鏈接的老的重複分組在該鏈接已終止後再現,從而被誤解成屬於同一鏈接的某個新的化身。爲作到這一點,TCP將不給處於TIME_WAIT狀態的鏈接發起新的化身。既然TIME_WAIT狀態的持續時間是MSL的2倍,這就足以讓某個方向上的分組最多存活MSL秒即被丟棄,另外一個方向上的應答最多存活MSL秒也被丟棄。經過實施這個規則,咱們就能保證每成功創建一個TCP鏈接時,來自該鏈接先前化身的老的重複分組都已在網絡中消逝了。

這個規則存在一個例外:若是到達的SYN的序列號大於前一化身的結束序列號,源自Berkeley的實現將給當前處於TIME_WAIT狀態的鏈接啓動新的化身。TCPv2第958~959頁對這種狀況有詳細的敘述。它要求服務器執行主動關閉,由於接收下一個SYN的那一端必須處於TIME_WAIT狀態。rsh命令具有這種能力。RFC 1185[Jacobson, Braden, and Zhang 1990]講述了有關這種情形的一些陷阱。

 


9:epool與select的區別

select在一個進程中打開的最大fd是有限制的,由FD_SETSIZE設置,默認值是2048。不過 epoll則沒有這個限制,它所支持的fd上限是最大能夠打開文件的數目,這個數字通常遠大於2048,通常來講內存越大,fd上限越大,1G內存都能達到大約10w左右。

 

select的輪詢機制是系統會去查找每一個fd是否數據已準備好,當fd不少的時候,效率固然就直線降低了,epoll採用基於事件的通知方式,一旦某個fd數據就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符,而不須要不斷的去輪詢查找就緒的描述符,這就是epool高效最本質的緣由。

 

不管是select仍是epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝就很重要,在這點上,epoll是經過內核於用戶空間mmap同一塊內存實現的,而select則作了沒必要要的拷貝

 


10:epool中et和lt的區別與實現原理

LT:水平觸發,效率會低於ET觸發,尤爲在大併發,大流量的狀況下。可是LT對代碼編寫要求比較低,不容易出現問題。LT模式服務編寫上的表現是:只要有數據沒有被獲取,內核就不斷通知你,所以不用擔憂事件丟失的狀況。

ET:邊緣觸發,效率很是高,在併發,大流量的狀況下,會比LT少不少epoll的系統調用,所以效率高。可是對編程要求高,須要細緻的處理每一個請求,不然容易發生丟失事件的狀況。

 

另外一點區別就是設爲ET模式的文件句柄必須是非阻塞的

附一個例子:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
/*
    setnonblocking - 設置句柄爲非阻塞方式
    */
int  setnonblocking( int  sockfd)
{
     if  (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
     {
         return  -1;
     }
     return  0;
}
/*
    handle_message - 處理每一個 socket 上的消息收發
    */
int  handle_message( int  new_fd)
{
     char  buf[MAXBUF + 1];
     int  len;
     /* 開始處理每一個新鏈接上的數據收發 */
     bzero(buf, MAXBUF + 1);
     /* 接收客戶端的消息 */
     len = recv(new_fd, buf, MAXBUF, 0);
     if  (len > 0)
     {
         printf ( "%d接收消息成功:'%s',共%d個字節的數據\n" ,
              new_fd, buf, len);
     }
     else
     {
         if  (len < 0)
             printf
                 ( "消息接收失敗!錯誤代碼是%d,錯誤信息是'%s'\n" ,
                  errno , strerror ( errno ));
         close(new_fd);
         return  -1;
     }
     /* 處理每一個新鏈接上的數據收發結束 */
     return  len;
}
int  main( int  argc, char  **argv)
{
     int  listener, new_fd, kdpfd, nfds, n, ret, curfds;
     socklen_t len;
     struct  sockaddr_in my_addr, their_addr;
     unsigned int  myport, lisnum;
     struct  epoll_event ev;
     struct  epoll_event events[MAXEPOLLSIZE];
     struct  rlimit rt;
     myport = 5000;
     lisnum = 2;
     /* 設置每一個進程容許打開的最大文件數 */
     rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
     if  (setrlimit(RLIMIT_NOFILE, &rt) == -1)
     {
         perror ( "setrlimit" );
         exit (1);
     }
     else
     {
         printf ( "設置系統資源參數成功!\n" );
     }
     /* 開啓 socket 監聽 */
     if  ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
     {
         perror ( "socket" );
         exit (1);
     }
     else
     {
         printf ( "socket 建立成功!\n" );
     }
     setnonblocking(listener);
     bzero(&my_addr, sizeof (my_addr));
     my_addr.sin_family = PF_INET;
     my_addr.sin_port = htons(myport);
     my_addr.sin_addr.s_addr = INADDR_ANY;
     if  (bind(listener, ( struct  sockaddr *) &my_addr, sizeof ( struct  sockaddr)) == -1)
     {
         perror ( "bind" );
         exit (1);
     }
     else
     {
         printf ( "IP 地址和端口綁定成功\n" );
     }
     if  (listen(listener, lisnum) == -1)
     {
         perror ( "listen" );
         exit (1);
     }
     else
     {
         printf ( "開啓服務成功!\n" );
     }
     /* 建立 epoll 句柄,把監聽 socket 加入到 epoll 集合裏 */
     kdpfd = epoll_create(MAXEPOLLSIZE);
     len = sizeof ( struct  sockaddr_in);
     ev.events = EPOLLIN | EPOLLET;
     ev.data.fd = listener;
     if  (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)
     {
         fprintf (stderr, "epoll set insertion error: fd=%d\n" , listener);
         return  -1;
     }
     else
     {
         printf ( "監聽 socket 加入 epoll 成功!\n" );
     }
     curfds = 1;
     while  (1)
     {
         /* 等待有事件發生 */
         nfds = epoll_wait(kdpfd, events, curfds, -1);
         if  (nfds == -1)
         {
             perror ( "epoll_wait" );
             break ;
         }
         /* 處理全部事件 */
         for  (n = 0; n < nfds; ++n)
         {
             if  (events[n].data.fd == listener)
             {
                 new_fd = accept(listener, ( struct  sockaddr *) &their_addr,&len);
                 if  (new_fd < 0)
                 {
                     perror ( "accept" );
                     continue ;
                 }
                 else
                 {
                     printf ( "有鏈接來自於: %d:%d, 分配的 socket 爲:%d\n" ,
                             inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
                 }
                 setnonblocking(new_fd);
                 ev.events = EPOLLIN | EPOLLET;
                 ev.data.fd = new_fd;
                 if  (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)
                 {
                     fprintf (stderr, "把 socket '%d' 加入 epoll 失敗!%s\n" ,
                             new_fd, strerror ( errno ));
                     return  -1;
                 }
                 curfds++;
             }
             else
             {
                 ret = handle_message(events[n].data.fd);
                 if  (ret < 1 && errno  != 11)
                 {
                     epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
                     curfds--;
                 }
             }
         }
     }
     close(listener);
     return  0;
}

  


11:寫一個server程序須要注意哪些問題

1)搜索網絡模型;

2)事件模型也能夠;

3)完成端口模型;

4)用select或異步select實現均可以;

簡單的話多線程就能夠解決,即每accept一個客戶端就建立一個線程,另外winsock 有5種I/O模型,Select、異步事件、事件選擇、重疊IO、完成端口,均可以實現必定數量的併發鏈接,建議你百度一下,都有現成的代碼實例,就幾個API調用,蠻簡單的,你看一下就會明白!

 

 

TCP窗口和擁塞控制實現機制

TCP數據包格式

Source Port

Destination port

Sequence Number

Acknowledgement Number

Length

Reserved

Control Flags

Window Size

Check sum

Urgent Pointer

Options

DATA

       

注:校驗和是對全部數據包進行計算的。

TCP包的處理流程

接收:

ip_local_deliver

tcp_v4_rcv() (tcp_ipv4.c)→_tcp_v4_lookup()

tcp_v4_do_rcv(tcp_ipv4.c)→tcp_rcv_state_process  (OTHER STATES)

↓Established

tcp_rcv_established(tcp_in→tcp_ack_snd_check,(tcp_data_snd_check,tcp_ack (tcp_input.c)    

↓         ↓                  ↓                         ↓

tcp_data                       tcp_send_ack               tcp_write_xmit 

↓ (slow)  ↓(Fast)                         ↓

tcp_data_queue                             tcp_transmit_skb

↓         ↓                                   ↓

sk->data_ready (應用層)                              ip_queque_xmit

 

發送:

send

tcp_sendmsg

__tcp_push_pending_frames       tcp_write_timer

↓                            ↓

tcp_ retransmit_skb

tcp_transmit_skb     

ip_queue_xmit

TCP段的接收

_tcp_v4_lookup()用於在活動套接字的散列表中搜索套接字或SOCK結構體。

tcp_ack (tcp_input.c)用於處理確認包或具備有效ACK號的數據包的接受所涉及的全部任務:

調整接受窗口(tcp_ack_update_window())

刪除從新傳輸隊列中已確認的數據包(tcp_clean_rtx_queue())

對零窗口探測確認進行檢查

調整擁塞窗口(tcp_may_raise_cwnd())

從新傳輸數據包並更新從新傳輸計時器

tcp_event_data_recv()用於處理載荷接受所需的全部管理工做,包括最大段大小的更新,時間戳的更新,延遲計時器的更新。

 

tcp_data_snd_check(sk)檢查數據是否準備完畢並在傳輸隊列中等待,且它會啓動傳輸過程(若是滑動窗口機制的傳輸窗口和擁塞控制窗口容許的話),實際傳輸過程由tcp_write_xmit()啓動的。(這裏進行流量控制和擁塞控制)

 

tcp_ack_snd_check()用於檢查能夠發送確認的各類情形,一樣它會檢查確認的類型(它應該是快速的仍是應該被延遲的)。

 tcp_fast_parse_options(sk,th,tp)用於處理TCP數據包報頭中的Timestamp選項。

 

tcp_rcv_state_process()用於在TCP鏈接不處在ESTABLISHED狀態的時候處理輸入段。它主要用於處理該鏈接的狀態變換和管理工做。

 

Tcp_sequence()經過使用序列號來檢查所到達的數據包是不是無序的。若是它是一個無序的數據包,測激活QuickAck模式,以儘量快地將確認發送出去。

 

Tcp_reset()用來重置鏈接,且會釋放套接字緩衝區。

 

TCP段的發送

tcp_sendmsg()(TCP.C)用於將用戶地址空間中的載荷拷貝至內核中並開始以TCP段形式發送數據。在發送啓動前,它會檢查鏈接是否已經創建及它是否處於TCP_ESTABLISHED狀態,若是尚未創建鏈接,那麼系統調用會在wait_for_tcp_connect()一直等待直至有一個鏈接存在。

tcp_select_window() 用來進行窗口的選擇計算,以此進行流量控制。

 

tcp_send_skb()(tcp_output.c)用來將套接字緩衝區SKB添加到套接字傳輸隊列(sk->write_queue)並決定傳輸是否能夠啓動或者它必須在隊列中等待。它經過tcp_snd_test()例程來做出決定。若是結果是負的,那麼套接字緩衝區就仍留在傳輸隊列中; 若是傳輸成功的話,自動傳輸的計時器會在tcp_reset_xmit_timer()中自動啓動,若是某一特定時間後無該數據包的確認到達,那麼計時器就會啓動。(還沒有找到調用之處)(只有在發送FIN包時纔會調用此函數,其它狀況下都不會調用此函數2007,06,15)

 

tcp_snd_test()(tcp.h) 它用於檢查TCP段SKB是否能夠在調用時發送。

 

tcp_write_xmit()(tcp_output.c)它調用tcp_transmit_skb()函數來進行包的發送,它首先要查看此時TCP是否處於CLOSE狀態,若是不是此狀態則能夠發送包.進入循環,從SKB_BUF中取包,測試是否能夠發送包(),接下來查看是否要分片,分片完後調用tcp_transmit_skb()函數進行包的發送,直到發生擁塞則跳出循環,而後更新擁塞窗口.

 

tcp_transmits_skb()(TCP_OUTPUT.C)負責完備套接字緩衝區SKB中的TCP段以及隨後經過Internet協議將其發送。而且會在此函數中添加TCP報頭,最後調用tp->af_specific->queue_xmit)發送TCP包.

 

tcp_push_pending_frames()用於檢查是否存在傳輸準備完畢而在常規傳輸嘗試期間沒法發送的段。若是是這樣的狀況,則由tcp_write_xmit()啓動這些段的傳輸過程。

 

TCP實例的數據結構

Struct tcp_opt    sock.h

包含了用於TCP鏈接的TCP算法的全部變量。主要由如下部分算法或協議機制的變量組成:

序列和確認號

流控制信息

數據包往返時間

計時器

數據包頭中的TCP選項

自動和選擇性數據包重傳

TCP狀態機

tcp_rcv_state_process()主要處理狀態轉變和鏈接的管理工做,接到數據報的時候不一樣狀態會有不一樣的動做。

tcp_urg()負責對緊急數據進行處理

 

創建鏈接

tcp_v4_init_sock()(tcp_ipv4.c)用於運行各類初始化動做:初始化隊列和計時器,初始化慢速啓動和最大段大小的變量,設定恰當的狀態以及設定特定於PF_INET的例程的指針。

tcp_setsockopt()(tcp.c)該函數用於設定TCP協議的服務用戶所選定的選項:TCP_MAXSEG,TCP_NODELAY, TCP_CORK, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT, TCP_SYNCNT, TCP_LINGER2, TCP_DEFER_ACCEPT和TCP_WINDOW_CLAMP.

tcp_connect()(tcp_output.c)該函數用於初始化輸出鏈接:它爲sk_buff結構體中的數據單元報頭預留存儲空間,初始化滑動窗口變量,設定最大段長度,設定TCP報頭,設定合適的TCP狀態,爲從新初始化計時器和控制變量,最後,將一份初始化段的拷貝傳遞給tcp_transmit_skb()例程,以便進行鏈接創建段的從新傳輸發送並設定計時器。

 

 

從發送方和接收方角度看字節序列域

以下圖示:

 

snd_una

snd_nxt

snd_una+snd+wnd

rcv_wup

rcv_nxt

rcv_wup+rcv_wnd

數據以經確認

數據還沒有確認

剩餘傳輸窗口

右窗口邊界

數據以經確認

數據還沒有確認

剩餘傳輸窗口

 

流控制

流控制用於預防接受方TCP實例的接受緩衝區溢出。爲了實現流控制,TCP協議使用了滑動窗口機制。該機制以由接受方所進行的顯式傳輸資信分配爲基礎。

滑動窗口機制提供的三項重要做業:

分段併發送於若干數據包中的數據集初始順序能夠在接收方恢復。

能夠經過數據包的排序來識別丟失的或重複的數據包。

兩個TCP實例之間的數據流能夠經過傳輸資信賦值來加以控制。

Tcp_select_window()

 當發送一個TCP段用以指定傳輸資信大小的時候就會在tcp_transmit_skb()方法中調用tcp_select_window()。當前通告窗口的大小最初是由tcp_receive_window()指定的。隨後經過tcp_select_window()函數來查看該計算機中還有多少可用緩衝空間。這就夠成了哪一個提供給夥伴實例的新傳輸資信的決定基礎。

   一旦已經計算出新的通告窗口,就會將該資信(窗口信息)存儲在該鏈接的tcp_opt結構體(tp->rcv_wnd)中。一樣,tp->rcv_wup也會獲得調整;它會在計算和發送新的資信的時候存儲tp->rcv_nxt變量的當前值,這也就是一般所說的窗口更新。這是由於,在通告窗口已發送以後到達的每個數據包都必須沖銷其被授予的資信(窗口信息)。

另外,該方法中還使用另外一種TCP算法,即一般所說的窗口縮放算法。爲了可以使得傳輸和接收窗口的16位數字運做起來有意義,就必須引入該算法。基於這個緣由,咱們引入了一個縮放因子:它指定了用以將窗口大小移至左邊的比特數目,利用值Fin tp->rcv_wscale, 這就至關於因子爲2F 的通告窗口增長。

 

Tcp_receive_window()(tcp.c)用於計算在最後一段中所授予的傳輸資信還有多少剩餘,並將自那時起接收到的數據數量考慮進去。

Tcp_select_window()(tcp_output.c)用於檢查該鏈接有多少存儲空間能夠用於接收緩衝區以及能夠爲接收窗口選擇多大的尺寸。

Tcp_space()肯定可用緩衝區存儲空間有多大。在所肯定的緩衝區空間進行一些調整後,它最後檢查是否有足夠的緩衝區空間用於最大尺寸的TCP段。若是不是這樣,則意味着已經出現了癡呆窗口綜合症(SWS)。爲了不SWS的出現,在這種情形下所授予的資信就不會小於協議最大段。

 若是可用緩衝區空間大於最大段大小,則繼續計算新的接收窗口。該窗口變量被設定爲最後授予的那個資信的值(tcp->rcv_wnd)。若是舊的資信大於或小於多個段大小的數量,則窗口被設定爲空閒緩衝區空間最大段大小的下一個更小的倍數。以這種方式計算的窗口值是做爲新接收資信的參考而返回的。

 

Tcp_probe_timer()(tcp_timer.c)是零窗口探測計時器的處理例程。它首先檢查同位體在一段較長期間上是否曾提供了一個有意義的傳輸窗口。若是有若干個窗口探測發送失敗,則輸出一個錯誤消息以宣佈該TCP鏈接中存在嚴重並經過tcp_done()關閉該鏈接。主要是用來對方通告窗口爲0,則每隔定時間探測通告窗口是否有效.

若是還未達到該最大窗口探測數目,則由tcp_send_probe0()發送一個沒有載荷的TCP段,且確認段此後將授予一個大於NULL的資信。

 

Tcp_send_probe0()(tcp_output.c)利用tcp_write_wakeup(sk)來生成併發送零窗口探測數據包。若是不在須要探測計時器,或者當前仍舊存在途中數據包且它們的ACK不含有新的資信,那麼它就不會獲得從新啓動,且probes_out和backoff 參數會獲得重置。

不然,在已經發送一個探測數據包後這些參數會被增量,且零窗口探測計時器會獲得從新啓動,這樣它在某一時間後會再次到期。

 

Tcp_write_wakeup()(tcp_output.c)用於檢查傳輸窗口是否具備NULL大小以及SKB中數據段的開頭是否仍舊位於傳輸窗口範圍之內。到目前爲止所討論的零窗口問題的情形中,傳輸窗口具備NULL大小,因此tcp_xmit_probe_skb()會在else分支中獲得調用。它會生成所需的零窗口探測數據包。若是知足以上條件,且若是該傳輸隊列中至少存在一個傳輸窗口範圍之內的數據包,則咱們極可能就碰到了癡呆綜合症的狀況。和當前傳輸窗口的要求對應的數據包是由tcp_transmit_skb()生成併發送的。

Tcp_xmit_probe_skb(sk,urgent)(tcp_output.c)會建立一個沒有載荷的TCP段,由於傳輸窗口大小NULL,且當前不得傳輸數據。Alloc_skb()會取得一個具備長度MAX_TCP_HEADER的套接字緩衝區。如前所述,將無載荷發送,且數據包僅僅由該數據包報頭組成。

Tcp_ack_probe() (tcp_input.c) 當接收到一個ACK標記已設定的TCP段且若是懷疑它是對某一零窗口探測段的應答的時候,就會在tcp_ack()調用tcp_ack_probe()。根據該段是否開啓了接受窗口,由tcp_clear_xmit_timer()中止該零窗口探測計時器或者由tcp_reset_xmit_timer()從新起用該計時器。

擁塞檢測、迴避和處理

流控制確保了發送到接受方TCP實例的數據數量是該實例可以容納的,以免數據包在接收方端系統中丟失。然而,該轉發系統中可能會出現緩衝區益處——具體來講,是在Internet 協議的隊列中,當它們清空的速度不夠快且到達的數據多於可以經過網絡適配器獲得發送的數據量的時候,這種狀況稱爲擁塞。

TCP擁塞控制是經過控制一些重要參數的改變而實現的。TCP用於擁塞控制的參數主要有:

(1) 擁塞窗口(cwnd):擁塞控制的關鍵參數,它描述源端在擁塞控制狀況下一次最多能發送的數據包的數量。

(2) 通告窗口(awin):接收端給源端預設的發送窗口大小,它只在TCP鏈接創建的初始階段發揮做用。

(3) 發送窗口(win):源端每次實際發送數據的窗口大小。

(4) 慢啓動閾值(ssthresh):擁塞控制中慢啓動階段和擁塞避免階段的分界點。初始值一般設爲65535byte。

(5) 迴路響應時間(RTT):一個TCP數據包從源端發送到接收端,源端收到接收端確認的時間間隔。

(6) 超時重傳計數器(RTO):描述數據包從發送到失效的時間間隔,是判斷數據包丟失與否及網絡是否擁塞的重要參數。一般設爲2RTT或5RTT。

(7) 快速重傳閾值(tcprexmtthresh)::能觸發快速重傳的源端收到重複確認包ACK的個數。當此個數超過tcprexmtthresh時,網絡就進入快速重傳階段。tcprexmtthresh缺省值爲3。

四個階段

1.慢啓動階段

舊的TCP在啓動一個鏈接時會向網絡發送許多數據包,因爲一些路由器必須對數據包進行排隊,所以有可能耗盡存儲空間,從而致使TCP鏈接的吞吐量(throughput)急劇降低。避免這種狀況發生的算法就是慢啓動。當創建新的TCP鏈接時,擁塞窗口被初始化爲一個數據包大小(一個數據包缺省值爲536或512byte)。源端按cwnd大小發送數據,每收到一個ACK確認,cwnd就增長一個數據包發送量。顯然,cwnd的增加將隨RTT呈指數級(exponential)增加:1個、2個、4個、8個……。源端向網絡中發送的數據量將急劇增長。

2.擁塞避免階段

當發現超時或收到3個相同ACK確認幀時,網絡即發生擁塞(這一假定是基於由傳輸引發的數據包損壞和丟失的機率小於1%)。此時就進入擁塞避免階段。慢啓動閾值被設置爲當前cwnd的一半;超時時,cwnd被置爲1。若是cwnd≤ssthresh,則TCP從新進入慢啓動過程;若是cwnd>ssthresh,則TCP執行擁塞避免算法,cwnd在每次收到一個ACK時只增長1/cwnd個數據包(這裏將數據包大小segsize假定爲1)。

3.快速重傳和恢復階段

當數據包超時時,cwnd被設置爲1,從新進入慢啓動,這會致使過大地減少發送窗口尺寸,下降TCP鏈接的吞吐量。所以快速重傳和恢復就是在源端收到3個或3個以上重複ACK時,就判定數據包已經被丟失,並重傳數據包,同時將ssthresh設置爲當前cwnd的一半,而沒必要等到RTO超時。圖2和圖3反映了擁塞控制窗口隨時間在四個階段的變化狀況。

 

TCP用來檢測擁塞的機制的算法:

一、  超時。

一旦某個數據包已經發送,重傳計時器就會等待一個確認一段時間,若是沒有確認到達,就認爲某一擁塞致使了該數據包的丟失。對丟失的數據包的初始響應是慢速啓動階段,它從某個特定點起會被擁塞回避算法替代。

二、  重複確認

重複確認的接受表示某個數據段的丟失,由於,雖然隨後的段到達了,但基於累積ACK段的緣由它們卻不能獲得確認,。在這種情形下,一般認爲沒有出現嚴重的擁塞問題,由於隨後的段其實是接收到了。基於這個緣由,更爲新近的TCP版本對經由慢速啓動階段的丟失問題並不響應,而是對經由快速傳輸和快速恢復方法的丟失做出反應。

 

慢速啓動和擁塞回避

Tcp_cong_avoid() (tcp_input.c)用於在慢速啓動和擁塞回避算法中實現擁塞窗口增加。當某個具備有效確認ACK的輸入TCP段在tcp_ack()中進行處理時就會調用tcp_cong_avoid()

首先,會檢查該TCP鏈接是否仍舊處於慢速啓動階段,或者已經處於擁塞回避階段:

  在慢速啓動階段擁塞窗口會增長一個單位。可是,它不得超過上限值,這就意味着,在這一階段,伴隨每一輸入確認,擁塞窗口都會增長一個單位。在實踐中,這意味着能夠發送的數據量每次都會加倍。

 在擁塞回避階段,只有先前已經接收到N個確認的時候擁塞窗口才會增長一個單位,其中N等於當前擁塞窗口值。要實現這一行爲就須要引入補充變量tp->snd_cwnd_cnt;伴隨每一輸入確認,它都會增量一個單位。下一步,當達到擁塞窗口值tp->snd_cwnd的時候,tp->snd_cwnd就會最終增長一個單位,且tp->snd_cwnd_cnt獲得重置。經過這一方法就能完成線形增加。

總結:擁塞窗口最初有一個指數增加,可是,一旦達到閾值,就存在線形增加了。

 

Tcp_enter_loss(sk,how) (tcp_input.c)是在重傳計時器的處理例程中進行調用的,只有重傳超時期滿時所傳輸的數據段還未獲得確認,該計時器就會啓動。這裏假定該數據段或它的確認已丟失。在除了無線網絡的現代網絡中,數據包丟失僅僅出如今擁塞情形中,且爲了處理緩衝區溢出的問題將不得不在轉發系統中丟棄數據包。重傳定時器定時到時調用,用來計算CWND和閾值重傳丟失數據,而且進入慢啓動狀態.

 

Tcp_recal_ssthresh() (tcp.h)一旦檢測到了某個擁塞情形,就會在tcp_recalc_ssthresh(tp)中從新計算慢速啓動階段中指數增加的閾值。所以,一旦檢測到該擁塞,當前擁塞窗口tp->snd_cwnd的大小就會被減半,並做爲新的閾值被返回。該閾值不小於2。

 

快速重傳和快速恢復

TCP協議中集成可快速重傳算法以快速檢測出單個數據包的丟失。先前檢測數據包丟失的惟一依據是重傳計時器的到期,且TCP經過減小慢速啓動階段中的傳輸速率來響應這一問題。新的快速重傳算法使得TCP可以在重傳計時器到期以前檢測出數據包丟失,這也就是說,當一連串衆多段中某一段丟失的時候。接收方經過發送重複確認的方法來響應有一個段丟失的輸入數據段。

LINUX內核的TCP實例中兩個算法的協做方式:

▉  當接收到了三個確認複本時,變量tp->snd_ssthresh就會被設定爲當前傳辦理窗口的一半.丟失的段會獲得重傳,且擁塞窗口tp->snd_cwnd會取值tp->ssthresh+3*MSS,其中MSS表示最大段大小.

▉  每接收到一個重複確認,擁塞窗口tp->snd_cwnd 就會增長一個最大段大小的值,且會發送一個附加段(若是傳輸窗口大小容許的話)

▉  當新數據的第一個確認到達時,此時tp->snd_cwnd就會取tp->snd_ssthresh的原如值,它存儲在tp->prior_ssthresh中.這一確認應該對最初丟失的那個數據段進行確認.另外還應該確認全部在丟失數據包和第三個確認複本之間發送的段.

 

TCP中的計時器管理

Struct timer_list

{  struct timer_head list;

   unsigned long expires;

   unsigned long data;

   void (*function) (unsighed long);

   volatile   int running;

}

tcp_init_xmit_timers()(tcp_timer.c)用於初始化一組不一樣的計時器。Timer_list會被掛鉤進來,且函數指針被轉換到相應的行爲函數。

Tcp_clear_xmit_timer()(tcp.h)用於刪除timer_list結構體鏈表中某個鏈接的全部計時器組。

Tcp_reset_xmit_timer(sk,what,when)(tcp.h)用於設定在what到時間when中指定的計時器。

 

TCP數據發送流程具體流程應該是這樣的:

tcp_sendmsg()----->tcp_push_one()/tcp_push()----

                |                              |

                |                             \|/

                |--------------->__tcp_push_pending_frames()

                                               |

                                              \|/

                                         tcp_write_xmit()

                                               |

                                              \|/

                                         tcp_transmit_skb()



tcp_sendmsg()-->__tcp_push_pending_frames() --> tcp_write_xmit()-->tcp_transmit_skb()

 

write_queue 是發送隊列,包括已經發送的但還未確認的和還從未發送的,send_head指向其中的從未發送之第一個skb。 另外,仔細看了看tcp_sendmsg(),以爲它仍是但願來一個skb 就發送一個skb,即儘快發送的策略。 
有兩種狀況, 
一,mss > tp->max_window/2,則forced_push()總爲真,這樣, 每來一個skb,就調用__tcp_push_pending_frames(),爭取發送。 
二,mss < tp->max_window/2,所以一開始forced_push()爲假, 但因爲一開始send_head==NULL,所以會調用tcp_push_one(), 而它也是調用__tcp_push_pending_frames(),若是此時網絡狀況 良好,數據會順利發出,並很快確認,從而send_head又爲NULL,這樣下一次數據拷貝時,又能夠順利發出。這就是說, 在網絡狀況好時,tcp_sendmsg()會來一個skb就發送一個skb。 
只有當網絡狀況出現擁塞或延遲,send_head不能及時發出, 從而不能走tcp_push_one()這條線,纔會看數據的積累,此時, 每當數據積累到tp->max_window/2時,就嘗試push一下。 
而當拷貝數據的總量不多時,上述兩種狀況均可能不會知足, 
這樣,在循環結束時會調用一次tcp_push(),即每次用戶的完整 
一次發送會有一次push。

 

TCP包接收器(tcp_v4_rcv)將TCP包投遞到目的套接字進行接收處理. 當套接字正被用戶鎖定, TCP包將暫時排入該套接字的後備隊列(sk_add_backlog). 這時若是某一用戶線程企圖鎖定該套接字(lock_sock), 該線程被排入套接字的後備處理等待隊列(sk->lock.wq). 當用戶釋放上鎖的套接字時(release_sock), 後備隊列中的TCP包被當即注入TCP包處理器(tcp_v4_do_rcv)進行處理, 而後喚醒等待隊列中最早的一個用戶來得到其鎖定權. 若是套接字未被上鎖, 當用戶正在讀取該套接字時, TCP包將被排入套接字的預備隊列(tcp_prequeue), 將其傳遞到該用戶線程上下文中進行處理.

 

 

TCP定時器(TCP/IP詳解2)

TCP爲每條鏈接創建七個定時器:

一、  鏈接創建定時器在發送SYN報文段創建一條新鏈接時啓動。若是沒有在75秒內收到響 應,鏈接創建將停止。

當TCP實例將其狀態從LISTEN更改成SYN_RECV的時侯就會使用這一計時器.服務端的TCP實例最初會等待一個ACK三秒鐘.若是在這一段時間沒有ACK到達,則認爲該鏈接請求是過時的.

 

二、  重傳定時器在TCP發送數據時設定.若是定時器已超時而對端的確認還未到達,TCP將重傳數據.重傳定時器的值(即TCP等待對端確認的時間)是動態計算的,取決於TCP爲該 鏈接測量的往返時間和該報文段已重傳幾回.

 

三、  延遲ACK定時器在TCP收到必須被確認但無需立刻發出確認的數據時設定.TCP等 待時間200MS後發送確認響應.若是,在這200MS內,有數據要在該鏈接上發送,延遲的ACK響應就可隨着數據一塊兒發送回對端,稱爲稍帶確認.

 

 

四、  持續定時器在鏈接對端通告接收窗口爲0,阻止TCP繼續發送數據時設定.因爲鏈接對端發送的窗口通告不可靠,容許TCP繼續發送數據的後續窗口更新有可能丟失.所以,若是TCP有數據要發送,但對端通告接收窗口爲0,則持續定時器啓動,超時後向對端發送1字節的數據,斷定對端接收窗口是否已打開.與重傳定時器相似,持續定時器的值也是動態計算的,取決於鏈接的往返時間,在5秒到60秒之間取值.

 

五、  保活定時器在應用進程選取了插口的SO_KEEPALIVE選項時生效.若是鏈接的連續空閒時間超過2小時,保活定時器超時,向對端發送鏈接探測報文段,強迫對端響應.若是收到了期待的響應,TCP肯定對端主機工做正常,在該鏈接再次空閒超過2小時以前,TCP不會再進行保活測試,.若是收到的是其它響應,TCP肯定對端主要已重啓.若是連紐若干次保活測試都未收到響應,TCP就假定對端主機已崩潰,儘管它沒法區分是主機幫障仍是鏈接故障.

 

 

六、  FIN_WAIT-2定時器,當某個鏈接從FIN_WAIT-1狀態變遷到FIN_WAIN_2狀態,而且不能再接收任何數據時,FIN_WAIT_2定時器啓動,設爲10分鐘,定時器超時後,從新設爲75秒,第二次超時後鏈接被關閉,加入這個定時器的目的爲了不若是對端一直不發送FIN,某個鏈接會永遠滯留在FIN_WAIT_2狀態.

 

七、  TIME_WAIT定時器,通常也稱爲2MSL定時器.2MS指兩倍MSL.當鏈接轉移到TIME_WAIT狀態,即鏈接主動關閉時,定時器啓動.鏈接進入TIME_WAIT狀態時,定時器設定爲1分鐘,超時後,TCP控制塊和INTERNET PCB被刪除,端口號可從新使用.

TCP包含兩個定時器函數:一個函數每200MS調用一次(快速定時器);另外一個函數每500MS調用一次.延遲定時器與其它6個定時器有所不一樣;若是某個鏈接上設定了延遲ACK定時器,那麼下一次200MS定時器超時後,延遲的ACK必須被髮送.其它的定時器每500MS遞減一次,計數器減爲0時,就觸發相應的動做.

相關文章
相關標籤/搜索