第十九章 APO連接與網絡v節點

                  第十九章   APO連接與網絡v節點

 

    在編寫網絡底層實現前,需要做許多的準備工作;除了修改前面的章節外,可能還需寫3章;本章、文件號管理類的實現、本地內存管理類的實現。我很希望最終的網絡編程能給我驚喜;IP/TCP/UDP/ICMP的實現、包括所有的網絡服務器的實現(HTTP、DNS、FTP等等),能到達300行的代碼量;多於300行代碼量那就更好了、我會很高興。網絡編程第一層簡爲socket層,第二層簡稱爲TCP層,第三層簡稱爲IP層;以後,不再說明。

     這章會寫得慢,因爲要仔細的研究那個:複雜的、巨大的、認真的、嚴肅的、搞笑的、牛角尖的、牛逼的TCP協議。

一、APO的連接

    什麼是連接?這簡單的問題要耗費我不少時間,簡單其實意味着複雜、是很多內容濃縮而形成。Socket、插座、電話號碼、端口,就算我們通過類比來描述一個通信連接過程;但能完全描述一個連接嗎?對於一個socket客戶端口,確實對應着一個客戶端連接。但對於同一個socket服務端口,可以有許多連接啊;顯然不行。使用雙方的IP地址、和socket端口來定義一個連接,那顯然可以;但那需要32字節啊,想快速定位一個連接是不行的。嗯、查了一下網上資料,天喲、真的是用什麼5元組、7元組來確定一個連接!五元組是: 源IP地址、目的IP地址、協議號、源端口、目的端口。七元組是: 源IP地址、目的IP地址、協議號、源端口、目的端口,服務類型及接口索引。

1、連接的表示

    對於客戶端,可以用16位socket客戶端口來表示;那麼、如何知道一個連接是客戶端還是服務端?我們需要建立一個端口表,並不大、因爲一個服務端口就對應很多連接;APO的表大小隻是8K項。那麼、表項的內容是什麼?應該是跟操作系統有關,我不清楚,或許它們不是使用端口表來表示。不管怎樣,接收了一個數據包,就應該判斷其屬於哪一個連接;想法與文件號sockfd關聯。APO的端口表項就是一個字,如果是客戶端口:高8位是狀態標誌、低24位是文件號sockfd;對於服務端口,高8位是狀態標誌、低24位是0;該端口項不用、全0。實際上,還需管理端口的位圖(用於端口的分配、釋放),及端口查詢表(表項內容就是端口值)。提取IP數據包的目標socket端口值,先在端口查詢表找出端口值在第i項(通常需要耗費約30ns);之後、再用i去端口表獲得文件號,如是0、則提取IP數據包的24位的服務端文件號字段。如果主機的MAC端口速度是1GBPS,包之間的最小間隔160ns;APO的最小包是512位、等效512ns;所以、在IP層處理一個IP數據包的最短時間只有672ns。IP層處理需要包含內存管理、IP數據包組裝成IP數據報、錯誤包處理、正確提交到TCP層(該層最複雜,連接、文件號管理、端口管理等,處理的最短時間只有672ns。)、ICMP報文處理等。APO的處理速度也就每秒1百多萬個數據包,支持千萬個連接應該問題不大。

    使用五元組,目的方不是本機嗎?三元組不行?嗯,它們不像APO主機是唯一的IP地址,是可以有多個IP地址。好了,五元組的字節數太多、直接比較耗費太大(千個連接就會耗盡inte類主機資源),想法每元抽取8位、形成32位哈希值(哈希算法簡單、只抽取4元、夠囉嗦的)。那不一定是唯一哈希值啊,嗯、增加衝突處理;我要暈了!也許千個連接還行,對付10萬個連接時,主機要歇菜。它們應該是用鏈表去關聯到文件號sockfd,對於複雜腦袋型的操作系統、我無法進行仔細探討,所以、只能是對它們推測。

    既然我們將socket看作文件,那麼爲何不直接使用文件號sockfd來代表一個連接呢?可以,APO就是這樣的。客戶端就不用說了;在服務端,我們用不同的文件號來代表不同的客戶端到同一個服務端的連接;而描述連接的是文件號相對應的內存v節點。但客戶端是不知道它們到服務端的連接、在服務端的相應文件號啊;所以,需要在IP包中使用到24位的服務端文件號字段了。服務端文件號字段是服務端設置,而客戶端發送數據報時、也將服務端文件號字段一起轉發就行了。這樣,客戶端的數據報到達服務端主機的網絡底層時,就可提取24位的服務端文件號字段、快速定位到相應的內存v節點,從而得到該連接的描述。網絡底層通常是先提取數據包的目標socket端口、查socket端口表,如是客戶端口,那就可以立即得到相應的文件號;如果是0(服務端口),那就需提取IP包頭的24位服務端文件號字段了;當然、進入相應的v節點後,還需進一步比較確認,那只是不到10ns的一次性比較吧。


2、建立連接


     信令必須可靠,TCP是使用三次握手連接,爲何?要保證“信令可靠”,就需要對一個信令數據包進行ack確認表示接收端收到。如果收不到ack,就要做超時重發、APO最多2次超時重發。在Linux下,默認重試次數爲5次,重試的間隔時間從1s開始每次都翻售,5次的重試時間間隔爲1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s都知道第5次也超時了,所以,總共需要63s,TCP纔會把斷開這個連接。我們知道一個512的信令包只是不到1us,就算路徑有100箇中間路由交換機的存儲轉發會產生一定的時延,從A到達B也不會到1ms、來回2ms;爲何需要重試時間間隔爲多於 1s呢?這不浪費時間嗎? 給黑客利用的機會?有點像脫褲子放屁喲?你可能說,如果server端接到了clien發的SYN後回了SYN-ACK後client就突然掉線了(或故意不發ACK、這概率更大),server端沒有收到client回來的ACK,那麼,應該給client端1分鐘、和重發5次的機會。要知道主機每秒處理1百萬個數據包,那麼1分鐘就6千萬個數據包啊;如果這些數據包都是請求連接信令包、主機的資源就給耗盡。一些惡意的人就爲此製造了SYNFlood攻擊——給服務器發了一個SYN後,就下線了,於是服務器需要默認等63s纔會斷開連接;這樣,攻擊者就可以把服務器的syn連接的隊列耗盡,讓正常的連接請求不能處理。於是,爲了糾正一個小錯誤,就要付出更爲複雜和耗時的代價。Linux下給了一個叫tcp_syncookies的 參數來應對這個事——當SYN隊列滿了後,TCP會通過源地址端口、目標地址端口和時間戳打造出一個特別的Sequence Number發回去(又叫cookie),如果是攻擊者則不會有響應,如果是正常連接,則會把這個 SYN Cookie發回來,然後服務端可以通過cookie建連接(即使你不在SYN隊列中)。代價就是,本可以每秒處理1百萬個數據包,可能變成每秒處理1萬個數據包。關於這些,linux還有不少的複雜處理方法;不再討論。我就想不通,爲何複雜腦袋的產物總是九曲十八彎的;就不能簡明些;協議也不應該是專爲垃圾主機制定的。

     MSS是最大傳輸大小的縮寫,就是TCP數據包每次能夠傳輸的最大數據報分段。一個數據報被分段成多個小於MTU的數據包(也叫數據報分段)。爲了達到最佳的傳輸效能TCP協議在建立連接的時候通常要協商雙方的MSS值,這個值TCP協議在實現的時候往往用MTU值代替(需要減去MAC頭18字節、IP數據包包頭的大小20Bytes和TCP數據段的包頭20Bytes)所以往往MSS爲1518 – 58 = 1460B。通常MTU是1536B,而傳輸一個TCP信令只是64B,浪費啊;3次握手、還要探查MTU和協商MSS值、測試往返時間,你說要建立一個連接需要多少次信令報往返?浪費多少資源?我以前遠看TCP協議、感覺嚴謹、偉大,就像充滿迷一樣的大石山;爲何、現在看上去就像垃圾山?漏洞百出、被黑客攻擊得遍體鱗傷,好搞笑啊!小學生水平都不如,卻複雜無比、扁扁我還看幾天才略懂;誒、矛盾的世界。

     在APO中,TCP是雙工連接、UDP是簡單型半雙工連接。APO的連接並非是通常的TCP三次握手連接,而是使用ICMP探查數據包來進行連接的;只是2次握手,請求、確認。當客戶端的請求連接ICMP探查數據包到達服務端時,探查數據包已經包含了沿途路由交換機的MTU、時間戳、IP地址標記等(共16字節);那麼、服務端就可知道客戶端的地球位置、路徑信息、IP地址、單向耗時,並做過濾分析:源IP地址、路由交換機的鏈路地址是否上了黑名單;如果安全,那麼服務端建立描述該連接的文件號、保存連接描述到內存v節點,轉發ICMP探查數據包,並在上面打上服務端文件號字段、分段大小MSS、允許的數據報大小、單向耗時、ACK確認等印記。APO網絡是無法假冒源地址的,除非你劫持了路由交換機的子網地址。但服務端規定了子網每秒發出到服務端的連接請求數不能超過1000個、主機每秒發出到服務端的連接請求數不能超過10個;黑客想SYN Flood攻擊是不可能的,要知道服務端可是允許千萬個連接啊;APO取消了廣播,你用組播服務器、還是相當於從一臺機器發包,沒用。那麼,劫持路由交換機的子網地址,每秒連接請求數1000個,連續n秒;也沒用,APO的重發時間是1s、上一批的1000個連接,如果沒發數據包、就被取消了連接。黑客啊,千萬別上黑名單,否則來多少數據包、就被丟掉還是小事;事後、追究、報警才大件事。那麼,使用招數:佔着茅坑不拉屎;一臺主機正常連接、每10分鐘發一個垃圾GET請求來保持連接。這也不行,服務端規定每臺主機只給50個連接;黑客需要劫持30多萬臺主機。如果主機用APO操作系統,你想劫持那麼多主機的root、只是做夢。APO信令報文只是最多發3次,重發時間間隔是,200ms、600ms,延遲到1s時、如果沒收到ACK(客戶端)或數據包(服務端)、就釋放了該連接。APO的探查ICMP數據包最多45E,只能最多90個路由交換機的印記;如果往返超過90個,那麼會有部分印記被消除。使用匯聚路由交換機,這種情形不會出現,再遠、往返也不會超過50個路由交換機。

     客戶端發送SYN連接請求ICMP探查數據包後、進入SYN_SENT狀態。服務端收到連接請求ICMP探查數據包後、轉發打上印記ACK+SYN的ICMP探查數據包、進入SYN_RECEIVED狀態;如果在1s內、再次收到相應客戶端的IP數據包,則進入ESTABLISHED連接建立狀態。否則、在1s內重發2次後,沒收到數據包;釋放該連接!客戶端收到確認ICMP探查數據包後,才確認該連接、進入ESTABLISHED狀態,並計算往返時間、確定MSS、初始化發送數據報的頭;發送第一個IP數據包。如果收到如目標不可到達等的ICMP差錯報文,釋放連接、返回錯誤消息給應用層。如果、超時則重發最多2次的請求連接ICMP探查數據包;如果在1s內沒收到服務端的確認ICMP探查數據包,則釋放連接、返回錯誤消息給應用層。

3、連接建立後的TCP通信

    TCP要保證所有的數據包都可以到達,所以,必需要有重傳機制。假如客戶端只是請求下載一個大小32GB的文件,TCP先是分割成512K個64KB的報文、再將每個報文分段成、比如1280B的52個IP數據包吧。我們假設一個IP數據包的往返時間是2ms,如果每發送一個IP數據包、都要來一個確認後,才發下一個數據包,那要暈倒!不說,ACK信令包浪費帶寬,可是要多花2倍的時間啊,學院派的大神們、終於臉紅了,羞愧了。還有啊,如果對方沒有ACK迴應、那還要等待超時重發,還要慘。TCP每發送一個報文段,就對此報文段設置一個超時重傳計時器。此計時器設置的超時重傳時間RTO(RetransmissionTime-Out)應當略大於TCP報文段的平均往返時延RTT,一般可取RTO = 2RTT;4ms? 但是,也可以根據具體情況人爲調整RTO的值,例如可以設置此超時重傳時間RTO = 90秒。當超過了規定的超時重傳時間還未收到對此TCP報文段的預期確認信息,則必須重新傳輸此TCP報文段。

重傳計時器(retransmission timer):當TCP發送報文段時,就創建該特定報文段的重傳計時器。可能發生兩種情況:
A: 若在計時器截止時間到(通常是60秒)之前收到了對此特定報文段的確認,則撤銷此計時器。
B: 若在收到了對此特定報文段的確認之前計時器截止期到,則重傳此報文段,並將計時器復位。

     TCP在重發的時候不是等間隔的,第一次和第二次發送之間是N秒,第二次和第三次之間是2N秒,一般N的初值是6秒吧,75秒內收不到迴應TCP層就認爲連接失敗(不同的系統可能這個值不同),這些值可能都是無法控制的,TCP自己有一套複雜的RTT測量算法,重發間隔就是基於這個算法;忽快忽慢應該和重發的不等間隔性質有關。

    以上的幾段是網上摘抄,我到現在都不明白具體的重傳時間!即使是6秒啊,那也可以發送好幾百萬個數據包了;學院派啊、真是不惜成本。下面是網上摘抄TCP的RTT算法。

  從前面的TCP的重傳機制我們知道Timeout的設置對於重傳非常重要,設長了,重發就慢,沒有效率,性能差;設短了,重發的就快,會增加網絡擁塞,導致更多的超時,更多的超時導致更多的重發。


  而且,這個超時時間在不同的網絡的情況下,有不同的時間,根本沒有辦法設置一個死的。只能動態地設置。爲了動態地設置,TCP引入了RTT——Round Trip Time,也就是一個數據包從發出去到回來的時間。這樣發送端就大約知道需要多少的時間,從而可以方便地設置Timeout—— RTO(Retransmission TimeOut),以讓我們的重傳機制更高效。聽起來似乎很簡單,好像就是在發送端發包時記下t0,然後接收端再把這個ack回來時再記一個t1,於是RTT = t1 – t0。沒那麼簡單,這只是一個採樣,不能代表普遍情況。

經典算法: 1)首先,先採樣RTT,記下最近好幾次的RTT值。 2)然後做平滑計算SRTT – Smoothed RTT。公式爲:(其中的 α 取值在0.8 到 0.9之間,這個算法英文叫Exponential weighted movingaverage,中文叫:加權移動平均)SRTT =( α * SRTT ) + ((1- α) * RTT)3)開始計算RTO。公式如下:RTO = min [ UBOUND, max [LBOUND, (β * SRTT) ] ]其中:

UBOUND是最大的timeout時間,上限值

LBOUND是最小的timeout時間,下限值

β 值一般在1.3到2.0之間。

  
Karn / Partridge 算法:

  但是上面的這個算法在重傳的時候會出有一個終極問題——你是用第一次的時間和ack回來的時候做RTT樣本,還是用重傳的時間和ACK的時間做RTT樣本?這個問題無論你先那頭都是按下葫蘆起了瓢。情況(a)是ack沒回來,所發重傳。如果你計算第一次發送和ACK的時間,那麼,明顯算大了。情況(b)是ack回來慢了,重傳不一會,之前ACK就回來了。如果你是算重傳的時間和ACK回來的時間,就會短了。


Karn / Partridge Algorithm:這個算法的最大特點是——忽略重傳,不把重傳的RTT做採樣(你看,你不需要去解決不存在的問題)。但是,這樣一來,又會引發一個大BUG——如果在某一時間,網絡閃動,突然變慢了,產生了比較大的延時,這個延時導致要重轉所有的包(因爲之前的RTO很小),於是,因爲重轉的不算,所以,RTO就不會被更新,這是一個災難。 於是Karn算法用了一個取巧的方式——只要一發生重傳,就對現有的RTO值翻倍(這就是所謂的Exponential backoff)。

Jacobson / Karels 算法:

  前面兩種算法用的都是“加權移動平均”,這種方法最大的毛病就是如果RTT有一個大的波動的話,很難被發現,因爲被平滑掉了。所以,1988年,又有人推出來了一個新的算法,這個算法叫Jacobson / Karels Algorithm。這個算法引入了最新的RTT的採樣和平滑過的SRTT的差距做因子來計算。 公式如下:(其中的DevRTT是Deviation RTT的意思)SRTT= SRTT+ α(RTT– SRTT)DevRTT= (1-β)*DevRTT+β*(|RTT-SRTT|)RTO= µ *SRTT + ∂ *DevRTT(其中:在Linux下,α = 0.125,β = 0.25, μ = 1,∂= 4 ——這就是算法中的“調得一手好參數”,nobody knows why, it justworks…) 最後的這個算法在被用在今天的TCP協議中。我真的暈倒了,那要耗去多少CPU資源啊?要爲之編寫多少代碼啊?能支持到1萬個連接嗎?你們的腦袋何必那樣複雜啊?要知道過了、就成腦殘啊!


    嗯,重傳機制、羞愧的學院派大神們、開始改進了;我們往下看,他們如何做。接收端給發送端的Ack確認只會確認最後一個連續的包,比如,發送端發了1,2,3,4,5一共五份數據,接收端收到了1,2,於是回ack 3,然後收到了4(注意此時3沒收到),此時的TCP會怎麼辦?我們要知道,SeqNum和Ack是以字節數爲單位,所以ack的時候,不能跳着確認,只能確認最大的連續收到的包,不然,發送端就以爲之前的都收到了。

  一種是不回ack,死等3,當發送方發現收不到3的ack超時後,會重傳3。一旦接收方收到3後,會ack 回 4——意味着3和4都收到了。

  但是,這種方式會有比較嚴重的問題,那就是因爲要死等3,所以會導致4和5即便已經收到了,而發送方也完全不知道發生了什麼事,因爲沒有收到Ack,所以,發送方可能會悲觀地認爲也丟了,所以有可能也會導致4和5的重傳。

  對此有兩種選擇:

一種是僅重傳timeout的包,也就是第3份數據。

另一種是重傳timeout後所有的數據,也就是第3,4,5這三份數據。

  這兩種方式有好也有不好。第一種會節省帶寬,但是慢,第二種會快一點,但是會浪費帶寬,也可能會有無用功。但總體來說都不好。因爲都在等timeout,timeout可能會很長。

    於是,TCP引入了一種叫Fast Retransmit 的算法,不以時間驅動,而以數據驅動重傳。也就是說,如果,包沒有連續到達,就ack最後那個可能被丟了的包,如果發送方連續收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳。

  比如:如果發送方發出了1,2,3,4,5份數據,第一份先到送了,於是就ack回2,結果2因爲某些原因沒收到,3到達了,於是還是ack回 2,後面的4和5都到了,但是還是ack回2,因爲2還是沒有收到,於是發送端收到了三個ack = 2的確認,知道了2還沒有到,於是就馬上重傳2。然後, 接收端收到了2,此時因爲3,4,5都收到了,於是ack回6。

    Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是重傳之前的一個還是重裝所有的問題。對於上面的示例來說,是重傳#2呢?還是重傳#2,#3,#4,#5呢?因爲發送端並不清楚這連續的3個ack(2)是誰傳回來的?也許發送端發了20份數據,是#6,#10,#20傳來的呢。這樣,發送端很有可能要重傳從2到20的這堆數據(這就是某些TCP的實際的實現);可見,這是一把雙刃劍。

 

   另外一種更好的方式叫:Selective Acknowledgment(SACK),這種方式需要在TCP頭裏加一個SACK的東西,ACK還是Fast Retransmit的ACK,SACK則是彙報收到的數據碎版。這樣,在發送端就可以根據回傳的SACK來知道哪些數據到了,哪些沒有到。於是就優化了Fast Retransmit的算法。當然,這個協議需要兩邊都支持。在 Linux下,可以通過tcp_sack參數打開這個功能。
  
  這裏還需要注意一個問題——接收方Reneging,所謂Reneging的意思就是接收方有權把已經報給發送端SACK裏的數據給丟了。這樣幹是不被鼓勵的,因爲這個事會把問題複雜化了,但是,接收方這麼做可能會有些極端情況,比如要把內存給別的更重要的東西。所以,發送方也不能完全依賴SACK,還是要依賴ACK,並維護Time-Out,如果後續的ACK沒有增長,那麼還是要把SACK的東西重傳,另外,接收端這邊永遠不能把SACK的包標記爲Ack。


  注意:SACK會消費發送方的資源,試想,如果一個攻擊者給數據發送方發一堆SACK的選項,這會導致發送方開始要重傳甚至遍歷已經發出的數據,這會消耗很多發送端的資源。

Duplicate SACK又稱D-SACK,其主要使用了SACK來告訴發送方有哪些數據被重複接收了。D-SACK使用了SACK的第一個段來做標誌,如果SACK的第一個段的範圍被ACK所覆蓋,那麼就是D-SACK。如果SACK的第一個段的範圍被SACK的第二個段覆蓋,那麼就是D-SACK。引入了D-SACK,有這麼幾個好處:

  1)可以讓發送方知道,是發出去的包丟了,還是回來的ACK包丟了。

  2)是不是自己的timeout太小了,導致重傳。

  3)網絡上出現了先發的包後到的情況(又稱reordering)

  4)網絡上是不是把我的數據包給複製了。

  知道這些東西可以很好得幫助TCP瞭解網絡情況,從而可以更好的做網絡上的流控;Linux下的tcp_dsack參數用於開啓這個功能。

 

    暈了吧!如果爲了掩蓋一個小小的謊言,可以使用更復雜的、更大的謊言;爲了糾正一個小小錯誤,可以犯更大的錯誤、使用更爲複雜的犯錯方式的話。如果這成爲真理、成爲聰明人之常情的話;我無語、佩服得五體投地。有更爲簡潔的!要在IP層的實現那章才介紹。大神們,你們就不能簡單的去思考?

    據說,如果你不瞭解TCP的滑動窗口這個事,你等於不瞭解TCP協議。那爲何不將其想象成一堆臃腫垃圾?我正是這樣,所以也不想深入的去理解!有關滑動窗口的事情、可以寫成一本書了。當紅臉叫獸得意非凡的吹噓“交通規則、糊塗窗口、小包傳輸”時,我看到的是巨大的代碼量、和低能的效率、少量的連接!蒼白搞笑的吹噓,我體起疙瘩、渾身無力;是那樣的無奈。大神們,別搞了;求你們了,我真的受不了。我不生氣、我不生氣! “牢騷太盛防腸斷,風物長宜放眼量。”我阿Q還不行?


4、連接的斷開

    在APO中,連接的斷開是簡單的、也沒有那麼多種狀態,也沒有什麼4次握手的概念。主動關閉(active close)端應用程序調用close,於是其TCP層發出FIN請求主動關閉連接,之後進入FIN_WAIT狀態;等待遠程TCP的ACK確認,收到ACK則釋放連接。如果1s內、並重發2次FIN請求,還是沒有收到ACK;也釋放連接。被動關閉(passive close)端TCP也是類似的;當被動關閉端TCP接到FIN後,就發出ACK以迴應FIN請求(它的接收也作爲文件結束符傳遞給上層應用程序),並進入TIME-WAIT:等待足夠的時間(1s內、如果再次收到FIN,會重發ACK;最多2次),以確保遠程TCP接收到連接斷開請求的確認ACK。不管怎樣,在1s後,都會自動釋放被動關閉端的連接。TIME_WAIT等待狀態,又叫做2MSL狀態。APO的服務端的多個客戶連接、是TCP層實現的,每秒可實現上百萬個連接;跟應用層無關,不需要那些listen()、accept()、connect()等垃圾。無須像流行TCP那樣,應用程序在2MSL時間內是無法再次使用同一個插口的,啓動httpd就會出現錯誤(插口被使用),也沒有服務器平靜時間的概念。

    在關閉連接後,也許該連接還有一些數據包在網絡中傳輸;但1s內應該都被IP層判斷無此連接而丟棄了,如果剛好新連接參數(客戶端口、IP地址)與舊連接的一樣、並收到這些遺留數據包,那會怎樣?我們是否能用流標籤去判斷、從而丟棄?還是其它方法?

    我們還應考慮下極端情形:假如一個客戶端A,和服務端B建立了一個連接。如果A關閉了連接,或許根本就不發FIN,或許發送了FIN、而B喝醉酒不響應,或許FIN丟失,或許B發了ACK、而A沒收到超時關閉;總之是A關閉了、B可能還是在ESTABLISHED狀態。如果客戶端A重複的上演這種情況,那麼服務端B將會出現大量的虛假的ESTABLISHED連接。如何解決?我們是否應該在協議上增加一個由服務器定義的隨機的32位唯一標識字段?或者這是否就可以用服務端sockfd文件號字段表示呢?這樣一來,客戶端的連接,肯定是對應着唯一的服務端sockfd文件號字段;如果,服務端向客戶端發出查詢一個連接是否真實存在時,客戶端就可拿端口對應的v節點內的服務端sockfd文件號字段與數據包的做比對;不一樣,那就返回虛假連接ACK應答了;服務端就可以釋放虛假連接。那麼,我們對於遺留數據包也同樣處理、從而丟棄。

    對於黑客主機的客戶端,是沒招的;但我們可以限制一臺主機對應一個服務端口,只給它50個連接。虛就虛着吧,又能飛天?我服務端可以有1千多萬個連接,怕誰?黑客搞搞震,不就浪費自己的電費、資源。黑客們,千萬不要讓你們的肉雞進入監視名單,那將是你們的噩夢。


二、網絡v節點


    什麼是內存v節點?內存v節點是大小爲4E = 128B的一個內存結構變量,APO中最多可以有16M個v節點。V節點與在內存中打開的文件是一一對應的,網絡sockfd文件號實際上也就是v節點號。我們將網絡socket看作是一個文件,所以、與網絡socket文件必定對應有一個網絡v節點。一定要牢記v節點就是代表在內存中打開的文件,每個v節點號、對應一個文件。v節點的內容就是對一個文件的屬性描述,對於網絡socket文件就是對一個連接的描述。


1、v節點號與普通文件號fd的區別


    我們必須搞清這2個概念的區別,對於網絡socket文件、與進程或線程是唯一對應的;所以,網絡socket文件號sockfd與v節點號是一致的。但是,對於在內存中打開的文件、對應一個唯一的v節點號;但可能對應着許多進程或線程。所以,爲了區分不同進程或線程在內存中打開同一個文件;我們使用了文件描述符、即文件號fd,和文件打開表項。文件號fd與進程的文件打開表項是一一對應的,或許2個進程有相同的文件號,但不一定對應相同的v節點、這取決於它們的文件打開表項。所以,我們只能說文件號fd代表的是進程的文件打開表項,在進程的文件打開表項有對v節點的引用,用戶定義的流容器的引用等等。對於普通文件的v節點就是對應文件的i節點屬性和其所屬父目錄中的目錄項屬性;請參看前面的章節。注意:網絡socket文件是沒有目錄項和i節點的,只有v節點;網絡v節點描述的是一個連接,是本章需要論述的。


2、描述一個連接的網絡v節點

    socket文件是在內存建立的、和其它類型的文件不一樣,無須i節點、目錄項,也不需要文件打開表項;只有不一樣的v節點,描述了一個連接。網絡v節點就是描述網絡通信雙方的標識和協議,包含通信協議頭及建立、或釋放連接的一些參數。

   
   APO中,帶連接的UDP協議和TCP的合一;所以,除了無連接的ICMP外,APO就只有一個TCP協議了。APO的TCP協議也想將應用層協議(HTTP、FTP、TFTP、TELNET、PRC等)的一部分柔和進來;前面的章節關於協議部分需要修改。APO的TCP協議與現有的不一樣,做了簡化及性能的加強;要快速多了,跟無連接的DUP差不了多少。取消了麻煩、低效的窗口、序號、確認號;使用了獨特、高效、高速的重傳機制。APO的最小數據包是64B + 4B(CRC)+ 8B(同步頭)= 76B;最大普通數據包是:23*64B + 12B = 1484B,最大ICMP的數據包是:47*32B + 12B = 1516B。路由交換機的MTU,如果小於1516B,不分段、不丟包,只是截斷。


BU4E socket_vnode{ // socket文件的網絡v節點。

  BU1E IPAPO{ // APO的IP頭。

   BU48  MDA; // 以太網目標地址

   BU48  MSA; // 以太網源地址

   BU16  TYPE; // 類型高5位和低11位的長度。

   BU8   TTL;  // 跳數限制。

   BU8   TOS;  // 8位傳輸優先級、流量類型。

   BU64  SLADD; // 源鏈路地址。SLADD + MSA = 源IP地址

   BU64  DLADD; // 目標鏈路地址。DLADD + MDA = 目標IP地址

  }

union{ // APO的TCP協議頭、1E;支持4GB的文件流傳輸。

   BU32  length; // 文件流總字節長度,不含頭部64字節(2E)。

   BU32  sheetoff;// 低26位片偏移、分段單位1片 = 64字節。

// sheetoff.31-26  request; 上層協議的請求方法標識。Read、write、GET、

// POST、PUT、DELETE、TRACE、CONNECT、PATCH、ABOR、ACCT等等;或自定義。

   BU8   trflag; // 傳輸控制標誌。

// trflag.7  MF; 更多分段標誌(more fragment),0、最後一個分段

// trflag.6  DF; 分段標誌:1、不分段,0、可以分段

// trflag.5  MULTICAST; 組播標誌,1、有效。

// trflag.4  SIGLL; 信令標誌:1、信令報文段,0、正常的DATA報文段傳輸。

// trflag.3  RQ; 請求標誌(SIGLL=1有效):1、請求報文段,0、響應報文段。

// trflag.2  SIGI; 1、連接信令,0、應用層協議的信令。

// trflag.1-0 SIGC; 關於連接信令的標識,0、丟失的數據包信息,1-3備用。

   BU24  flowlabel; // 24位流標籤,標識不同的數據報。

   BU8   conflag; // 連接控制標誌。

// conflag.7  CWR; 用來表明它接收到了設置ECE標誌的TCP包。

// conflag.6  ECE; 網絡擁擠標誌。

// conflag.5  URG; 1、緊急指針字段有效。

// conflag.4  ACK; 1、確認應答。

// conflag.3  PSR; 1、推送,不等組裝、直接提交報文段到應用層。

// conflag.2  RST; 1、復位連接。

// conflag.1  SYN; 1、請求建立連接。

// conflag.0  FIN; 1、釋放連接。

   BU24  servesockfd; // 24位服務端的socket文件號。  

   BU16  SPORT; // 源端口

   BU16  DPORT; // 目的端口

   BU16  cheksum; // 校驗和。

   BU16  urgentp; // 緊急指針。

   BU16  tranmode; // 高層協議的傳輸模式。

   BU16  staterp; // 高層協議的狀態響應碼,或錯誤碼。

   BU32  usedata; // 用戶自定義、或高層協議使用、或組播域定義。

  }

union{ // APO的ICMPAPO協議頭、1E,無連接。

   BU8   imcptype; // 報文類型:宣告、鄰居請求、探查。

   BU8   segnum; // 路由交換機附帶數據項印記指針(項數、每項16字節)。

   BU16  devtype;// 源設備類型(主機,2-3-4層交換機等)、網絡中的級數。 

   BU32  PTID;  // 源設備的進程號(或端口號)、線程號(或標識)。

   BU32  times; // 報文發送時的時間戳us。

   BU16  MTU;   // 源設備的MTU。

   BU16  cheksum;// 校驗和。

   BU16B icmpdata; // 第0項的附帶數據,後面接着是更多的附帶數據。

   }

  BU2E{ // 2行的連接參數描述。

   BU32  recvstream_p; // 接收的數據報流容器本地內存空間指針。

   BU32  recvstream_len;// 流容器對象的大小,單位E

   BU16  FLAG; // 協議、狀態標誌。

   BU16  BACKLOG;// 最大連接數、單位爲256個連接。

   BU32  RECVLEN;// 低24位,允許的最大報文長度單位E,高8位附加狀態信息。

   BU32  PTID; // 高16位關聯的進程號、低16位關聯的線程號。

   BU32  SRCOUNT; // 發送、接收數據報的計數。

   BU32  crtime; // 連接創建時間標識,單位mS。

// 36B,定時器等參數,等待設計、定義。   

   }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章