計算機網絡

關於 TCP 併發鏈接的幾個思考題與試驗css

TCP

源端口號與目的端口號: 源端口號與目的端口號, 加上IP首部的源IP地址和目的IP地址惟一肯定一個TCP鏈接
序號: 一次TCP通訊(從TCP鏈接創建到斷開)過程當中某一個傳輸方向上的字節流編號
確認號: 僅當ACK標誌位1時有效. 表示指望下一個字節的序號
頭部長度: 標識TCP頭部有多少個32bit(4字節). 由於4位最大能表示15, 因此TCP頭部長度是60字節
保留位: 6位, 必須全爲0html

6個標誌位:node

  1. URG: 緊急指針是否有效
  2. ACK: 確認序號是否有效
  3. PSH: 接收方應儘快將這個報文段從TCP接收緩衝區中讀走
  4. RST: 表示要求對方中心創建鏈接. 稱攜帶RST標誌的TCP報文爲復位報文段
  5. SYN: 請求創建鏈接. 稱攜帶SYN標誌的TCP報文爲同步報文段
  6. FIN: 表示通知對方本端要關閉鏈接. 稱攜帶FIN標誌的TCP報文爲結束報文段

16位窗口大小: 經過窗口大小來達到流量控制git

檢驗和: 由發送端填充, 接收端對TCP報文執行CRC算法以檢驗TCP報文在傳輸過程當中是否損壞.github

TCP特色:web

  1. 基於字節流 --> 邊界問題, 粘包問題
  2. 面向鏈接
  3. 可靠傳輸
  4. 緩衝傳輸
  5. 全雙工
  6. 流量控制 --> 窗口機制

選項與填充(選項爲4字節整數倍, 不然用0填充)
最多見的可選字段是最長報文大小MSS(Maximum Segment Size), 每一個鏈接方一般都在通訊的第一個報文段中指明這個選項. 它指明本端所能接受的最大長度的報文. 若是該選項不設置, 默認爲536(20+20+536=576字節的IP數據報)面試

狀態流轉

若是兩邊同時斷鏈接, 那就會就進入到 CLOSING狀態, 而後到達TIME WAIT狀態算法

CLOSED: 表示初始狀態
LISTEN: 表示服務器端的某個socket處於監昕狀態, 能夠接受鏈接
SYN_SENT: 在服務端監昕後, 客戶端socket執行CONNECT鏈接時, 客戶端發送SYN報文, 此時客戶端就進入SYN_SENT狀態, 等待服務端的確認
SYN_RCVD: 表示服務端接收到了SYN報文, 在正常狀況下, 這個狀態是服務器端的socket在創建TCP鏈接時的3次握手會話過程當中的一箇中間狀態, 很短暫
ESTABLISHED: 表示鏈接已經創建了
FIN_WAIT_1: 這個是已經創建鏈接以後, 其中一方請求終止鏈接, 等待對方的FIN報文. FIN_WAIT_l狀態是當socket在ESTABLISHED狀態時, 它想主動關閉鏈接, 向對方發送了FIN報文, 此時該socket即進入到FIN_WAIT_l_狀態. 而當對方迴應ACK報文後, 則進入到FIN_WAIT_2狀態, 在實際的正常狀況下, 不管對方處於何種狀況, 都應該立刻迴應ACK報文因此FIN_WAIT_l狀態通常是比較難見到
FIN_WAIT_2: 實際上FIN_WAIT_2狀態下的socket, 表示半鏈接, 即有一方要求關閉鏈接, 但另外還告訴對方: 我暫時有數據須要傳送給你, 請稍後再關閉鏈接
TIME_WAIT: 表示收到了對方的FIN報文, 併發送出了ACK報文, 就等2MSL. 後便可回到CLOSED可用狀態了. 若是在FIN_WAIT_l狀態下收到了對方同時帶FIN標誌和ACK標誌的報文時, 能夠直接進入到TIME_WAIT狀態, 而無需通過FIN_WAIT_2狀態
CLOSING: 這種狀態比較特殊實際狀況中應該是不多見屬於一種比較罕見的例外狀態. 正常狀況下, 當發送FIN報文後, 按理來講是應該先收到(或同時收到)對方的ACK報文, 再收到對方的FIN報文. 可是 CLOSING 狀態表示你發送FIN報文後, 並無收到對方的ACK報文, 反而收到了對方的FIN報文. 爲何會出現此種狀況呢?其實細想一下, 也不可貴出結論:那就是若是雙方几乎在同時關閉一個 socket 的話, 那麼就出現了雙方同時發送 FIN 報文的狀況, 就會出現 CLOSING 狀態, 表示雙方都正在關閉 socket 鏈接. 
CLOSE_WAIT: 這種狀態的含義實際上是表示在等待關閉. 怎麼理解呢?當對方關閉一個socket後發送FIN報文給本身時, 系統將毫無疑問地會迴應一個ACK報文給對方, 此時則進入到CLOSE_WAIT狀態.  接下來呢, 實際上你真正須要考慮的事情是察看你是否還有數據發送給對方, 若是沒有, 那麼你也就能夠關閉這個 socket 了, 發送 FIN 報文給對方, 即關閉鏈接.  在 CLOSE WAIT 狀態下, 須要完成的事情是等待你去關閉鏈接. 
LAST_ACK: 這個狀態仍是比較好理解的, 它是被動關閉一方在發送FIN報文後, 最後等待對方的ACK報文 . 
CLOSED: 當收到ACK報文後, 也便可以進入到 CLOSED 可用狀態了

FIN_WAIT_2的設定時間

當TCP主動關閉一端調用了close()來執行鏈接的徹底關閉時會執行如下流程, 本端發送FIN給對端, 對端回覆ACK, 本端進入FIN_WAIT_2狀態, 此時只有對端發送了FIN, 本端纔會進入TIME_WAIT狀態, 爲了防止對端不發送關閉鏈接的FIN包給本端, 將會在進入FIN_WAIT_2狀態時, 設置一個FIN_WAIT_2定時器, 若是該鏈接超過必定時限, 則進入CLOSE狀態;chrome

注意:上述是針對close調用徹底關閉鏈接的狀況, shutdown執行半關閉不會啓動FIN_WAIT_2定時器;數據庫

TIME_WAIT

從上圖可知, 客戶端鏈接在收到服務器的結束報文以後, 並無直接進入CLOSED狀態, 而是轉義到TIME_WAIT狀態. 在這個狀態, 客戶端鏈接要等待一段長爲2MSL(Maximum Segment Life, 報文段最大生存時期)的時間, 才能徹底關閉. MSL是TCP報文段在網絡中的最大生存時期, 標準文檔建議是2min

TIME_WAIT狀態的存在緣由有兩點:

  1. 可靠地終止TCP鏈接
  2. 保證讓遲來的TCP報文段有足夠的時間識別並丟棄

第一個緣由很好理解. 假設圖中用於確認服務器接收報文段6的TCP報文段7丟失, 那麼服務器將從新發結束報文段(報文段6). 所以客戶端須要停留在某個狀態以處理重複收到的結束報文(即向服務器發送確認報文段). 不然, 客戶端將以復位報文段迴應服務器, 服務器認爲這是一個錯誤, 由於它指望的是一個想TCP報文段7那樣的確認報文段.

在Linux系統上, 一個TCP端口不能同時被打開屢次(兩次及以上). 當一個TCP鏈接處於TIME_WAIT狀態時, 將沒法當即使用該鏈接佔用着的端口來創建一個新鏈接. 反過來思考, 若是TIME_WAIT狀態不存在, 則應用程序可以當即創建一個和剛關閉的鏈接類似的鏈接(這裏說類似, 是指他們具備相同的IP和端口號). 這個新的和原來類似的鏈接被稱爲原來的鏈接的化身. 新的化身可能接受屬於原來的鏈接, 攜帶應用程序數據的TCP報文段(遲到的報文段), 這顯然是不該該發生的. 這就是TIME_WAIT狀態存在的第二個緣由

另外, 由於TCP報文段的最大生存時間是MSL, 因此堅持2MSL時間的TIME_WAIT狀態可以確保網絡上兩個傳輸方向上還沒有被接收到的, 遲到的TCP報文段都已消失(被中轉路由丟棄). 所以一個鏈接的新的化身能夠在2MSL時間以後安全地創建, 而絕對不會接受到屬於原來鏈接的應用程序數據, 這個是TIME_WAIT狀態要持續2MSL時間的緣由

有時但願避免TIME_WAIT狀態, 由於當程序退出後, 但願可以當即重啓它. 但因爲出在TIME_WAIT狀態的鏈接還佔用着端口, 程序沒法啓動(直到2MSL超時時間結束). 對客戶端程序來講一般不須要擔憂重啓問題. 由於客戶端通常使用系統自動分配的臨時端口號來創建鏈接, 而因爲隨機性, 臨時端口號通常和抽象上一次使用的端口號(還處於TIME_WAIT狀態的那個鏈接使用的端口號)不一樣, 因此客戶端程序通常能夠當即重啓

可是若是是服務器主動關閉鏈接後異常終止, 則由於它老是使用同一個知名服務端口號, 因此鏈接的TIME_WAIT狀態將致使它不能當即重啓. 不過, 能夠經過socket選項SO_REUSERADDR來強制進程當即使用處於TIME_WAIT狀態的鏈接佔用的端口

半關閉狀態

TCP鏈接時全雙工的, 因此它容許兩個方向的數據傳輸被獨立關閉. 換而言之, 通訊的一端能夠發送結束報文段給對方, 告訴他本端已經完成了數據的發送, 但容許繼續接收來自對方的數據, 直到對方也發送結束報文段以關閉鏈接. TCP鏈接的這種狀態稱爲半關閉狀態

圖中服務器和客戶端應用程序判斷對方是否已經關閉鏈接的方法是: read系統調用返回0(收到結束報文段). Linux還提供其餘檢驗鏈接是否被對方關閉的方法

socket網絡編程接口經過shutdown函數提供了對半關閉的支持. 這裏強調一下, 雖然介紹了半關閉狀態, 可是使用半關閉的應用程序不多見

報文復位段

在某些特殊條件下, TCP鏈接的一端會向另外一端發送攜帶RST標誌的報文段, 即復位報文段, 以通知對方關閉鏈接或從新創建鏈接
由於復位報文的接收通告窗口大小爲0, 因此能夠碰見: 收到復位報文段的一端應該關閉該鏈接或者從新鏈接, 而不能迴應這個報文段

  1. 當客戶端程序訪問一個不存在的端口時, 目標主機將給它發送一個復位報文段.
  2. 當客戶端程序向服務器端的否個端口發起鏈接, 而該端口仍被處於TIME_WAIT狀態的鏈接所佔用時, 客戶端程序也將收到復位報文段
  3. 異常終止鏈接. 異常終止一個鏈接, 即給對方一個復位報文段. 一旦發送了復位報文段, 發送端全部排隊等待發送的數據都將被丟棄, 應用程序能夠使用socket選項SO_LINGER來發送復位報文段, 以異常終止一個鏈接
  4. 處理半打開鏈接
      考慮下面的狀況: 服務器(或客戶端)關閉或者異常終止了鏈接, 而對方沒有接收到結束報文段(好比發生了網絡故障), 此時客戶端(或服務器)還維持着原來的鏈接, 而服務器(或客戶端)即便重啓, 也已經沒有該鏈接的任何消息了. 將這種狀態稱爲半打開狀態, 處於這種狀態的鏈接稱爲半打開鏈接. 若是客戶端(或服務器)往處於半打開狀態的鏈接寫入數據, 對方將回應一個復位報文段

滑動窗口

TCP的滑動窗口主要有兩個做用: 一是提供TCP的可靠性; 二是提供TCP的流控特性. 同時滑動窗口機制還體現了 TCP 面向字節流的設計思路.

對於TCP會話的發送方, 任什麼時候候在其發送緩存內的數據均可以分爲4類:

  1. 已經發送並獲得對端 ACK
  2. 已經發送但還未收到對端 ACK
  3. 未發送但對端容許發送
  4. 未發送且對端不容許發送
    其中「已經發送但還未收到對端ACK"和"未發送但對端容許發送的"這兩部分數據稱之爲發送窗口

對於TCP的接收方, 在某一時刻在它的接收緩存內存在3種狀態:

  1. 已接收;
  2. 未接收準備接收
  3. 未接收並未準備接收.(因爲ACK直接由TCP協議找回復, 默認無應用延遲, 不存在「已接收未回覆ACK")
    其中「未接收準備接收」稱之爲接收窗口

TCP 是雙工的協議, 會話的雙方均可以同時接收、發送數據. TCP會話的雙方都各自維護一個「發送窗口」和一個「接收窗口」. 其中各自的「接收窗口」大小取決於應用、系統、硬件的限制(TCP 傳輸速率不能大於應用的數據處理速率). 各自的「發送窗口」則要求取決於對端通告的「接收窗口」, 要求相同

滑動窗口實現面向流的可靠性來源於「確認重傳」機制. TCP 的滑動窗口的可靠性也是創建在「確認重傳」基礎上的. 發送窗口只有收到對端對於本段發送窗口內字節的ACK確認, 纔會移動發送窗口的左邊界. 接收窗口只有在前面全部的段都確認的狀況下才會移動左邊; 在前面還有字節未接收但收到後面字節的狀況下, 窗口不會移動, 並不對後續字節確認. 以此確保對端會對這些數據重傳

滑動窗口協議: 用於流量控制, 便可用於應用層也能夠用於傳輸層, 前者以幀爲單位進行確認, 後者以字節爲單位進行確認. 發送端發送的數據不能超過對方接收窗口的緩衝區大小, 若是超過接收窗口大小就會致使數據的丟失

爲什須要三次握手?

《計算機網絡》第四版中講「三次握手」的目的是「爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端, 於是產生錯誤」, 書中的例子是這樣的, 「已失效的鏈接請求報文段」的產生在這樣一種狀況下:client發出的第一個鏈接請求報文段並無丟失, 而是在某個網絡結點長時間的滯留了, 以至延誤到鏈接釋放之後的某個時間纔到達server. 原本這是一個早已失效的報文段. 但server收到此失效的鏈接請求報文段後, 就誤認爲是client再次發出的一個新的鏈接請求. 因而就向client發出確認報文段, 贊成創建鏈接. 假設不採用「三次握手」, 那麼只要server發出確認, 新的鏈接就創建了. 因爲如今client並無發出創建鏈接的請求, 所以不會理睬server的確認, 也不會向server發送數據. 但server卻覺得新的運輸鏈接已經創建, 並一直等待client發來數據. 這樣, server的不少資源就白白浪費掉了. 採用「三次握手」的辦法能夠防止上述現象發生. 例如剛纔那種狀況, client不會向server的確認發出確認. server因爲收不到確認, 就知道client並無要求創建鏈接. 」. 主要目的防止server端一直等待, 浪費資源.

鏈接創建3次握手

  1. 第一次握手: 創建鏈接時, 客戶端發送SYN包(SYN=J)到服務器端, 並進入SYN_SEND狀態, 等待服務器確認
  2. 第二次握手: 服務器收到SYN包, 必須確認客戶的SYN(ACK=J+1), 同時本身也發送一個SYN包(SYN=K), 即SYN+ACK包, 此時服務器進入SYN_RECV狀態
  3. 第三次握手: 客戶端收到服務器的SYN+ACK包, 向服務器發送確認包ACK(ACK=K+1), 此包發送完畢, 客戶端和服務器端進入ESTABLISHED狀態, 完成3次握手

鏈接斷開4次揮手

爲何建鏈接要3次握手, 斷鏈接須要4次揮手?

  1. 對於建鏈接的3次握手, 主要是要初始化Sequence Number的初始值. 通訊的雙方要互相通知對方本身的初始化的Sequence Number, 也就上圖中的J和K. 這個號要做爲之後的數據通訊的序號, 以保證應用層接收到的數據不會由於網絡上的傳輸問題而亂序(TCP 會用這個序號來拼接數據)
  2. 對於4次揮手, 其實仔細看則是兩次, 由於TCP是全雙工的, 因此, 發送方和接收方都須要FIN和ACK. 只不過, 有一方是被動的, 因此看上去就成了所謂的4次揮手.

解釋RTO,RTT和超時重傳?

- 超時重傳:發送端發送報文後若長時間未收到確認的報文則須要重發該報文。可能有如下幾種狀況:
  發送的數據沒能到達接收端,因此對方沒有響應。
  接收端接收到數據,可是ACK報文在返回過程當中丟失。
  接收端拒絕或丟棄數據。

  • RTO:從上一次發送數據,由於長期沒有收到ACK響應,到下一次重發之間的時間。就是重傳間隔。
      一般每次重傳RTO是前一次重傳間隔的兩倍,計量單位一般是RTT。例:1RTT,2RTT,4RTT,8RTT......
      重傳次數到達上限以後中止重傳。

  • RTT:數據從發送到接收到對方響應之間的時間間隔,即數據報在網絡中一個往返用時。大小不穩定。

如何區分流量控制和擁塞控制?

流量控制屬於通訊雙方協商;擁塞控制涉及通訊鏈路全局。
流量控制須要通訊雙方各維護一個發送窗、一個接收窗,對任意一方,接收窗大小由自身決定,發送窗大小由接收方響應的TCP報文段中窗口值肯定;擁塞控制的擁塞窗口大小變化由試探性發送必定數據量數據探查網絡情況後而自適應調整。
實際最終發送窗口 = min{流控發送窗口,擁塞窗口}

流量控制原理?

目的是接收方經過TCP頭窗口字段告知發送方本方可接收的最大數據量,用以解決發送速率過快致使接收方不能接收的問題。因此流量控制是點對點控制。

TCP是雙工協議,雙方能夠同時通訊,因此發送方接收方各自維護一個發送窗和接收窗。
  發送窗:用來限制發送方能夠發送的數據大小,其中發送窗口的大小由接收端返回的TCP報文段中窗口字段來控制,接收方經過此字段告知發送方本身的緩衝(受系統、硬件等限制)大小。
  接收窗:用來標記能夠接收的數據大小。

TCP是流數據,發送出去的數據流能夠被分爲如下四部分:已發送且被確認部分 | 已發送未被確認部分 | 未發送但可發送部分 | 不可發送部分,其中發送窗 = 已發送未確認部分 + 未發但可發送部分。接收到的數據流可分爲:已接收 | 未接收但準備接收 | 未接收不許備接收。接收窗 = 未接收但準備接收部分。

發送窗內數據只有當接收到接收端某段發送數據的ACK響應時才移動發送窗,左邊緣緊貼剛被確認的數據。接收窗也只有接收到數據且最左側連續時才移動接收窗口。

流量控制與擁塞控制

擁塞控制
  擁塞控制一般表示的是一個全局性的過程,它會涉及到網絡中全部的主機、
  全部的路由器和下降網絡傳輸性能的全部因素
流量控制
  流量控制發生在發送端和接收端之間,只是點到點之間的控制

TCP之 流量控制(滑動窗口)和 擁塞控制(擁塞控制的工做過程)

1.什麼是流量控制

防止發送方發的太快,耗盡接收方的資源,從而使接收方來不及處理

2.流量控制的一些知識點

(1)接收端抑制發送端的依據:接收端緩衝區的大小
(2)流量控制的目標是接收端,是怕接收端來不及處理
(3)流量控制的機制是丟包

3.怎麼樣實現流量控制?

使用滑動窗口
滑動窗口
1.滑動窗口是什麼?
滑動窗口是相似於一個窗口同樣的東西,是用來告訴發送端能夠發送數據的大小或者說是窗口標記了接收端緩衝區的大小,這樣就能夠實現
ps:窗口指的是一次批量的發送多少數據
2.爲何會出現滑動窗口?
在確認應答策略中,對每個發送的數據段,都要給一個ACK確認應答,收到ACK後再發送下一個數據段,這樣作有一個比較大的缺點,就是性能比較差,尤爲是數據往返的時間長的時候使用滑動窗口,就能夠一次發送多條數據,從而就提升了性能
3.滑動窗口的一些知識點
(1)接收端將本身能夠接收的緩衝區大小放入TCP首部中的「窗口大小」字段,經過ACK來通知發送端
(2)窗口大小字段越大,說明網絡的吞吐率越高
(3)窗口大小指的是無需等待確認應答而能夠繼續發送數據的最大值,即就是說不須要接收端的應答,能夠一次連續的發送數據
(4)操做系統內核爲了維護滑動窗口,須要開闢發送緩衝區,來記錄當前還有那些數據沒有應答,只有確認應答過的數據,才能從緩衝區刪掉
ps:發送緩衝區若是太大,就會有空間開銷
(5)接收端一旦發現本身的緩衝區快滿了,就會將窗口大小設置成一個更小的值通知給發送端,發送端收到這個值後,就會減慢本身的發送速度
(6)若是接收端發現本身的緩衝區滿了,就會將窗口的大小設置爲0,此時發送端將再也不發送數據,可是須要按期發送一個窗口探測數據段,使接收端把窗口大小告訴發送端
4.滑動窗口的優勢
能夠高效可靠的發送大量的數據

擁塞控制

擁塞控制與流量控制的區別
擁塞控制是防止過多的數據注入到網絡中, 能夠使網絡中的路由器或鏈路不致過載, 是一個全局性的過程. 目的: 避免網絡擁塞
流量控制是點對點通訊量的控制, 是一個端到端的問題, 主要就是抑制發送端發送數據的速率, 以便接收端來得及接收. 目的: 防止接收方緩存溢出致使分組丟失

擁塞控制四個部分: 慢啓動, 擁塞避免, 快速重傳, 快速恢復

  • 慢啓動
    1.慢開始不是指cwnd的增加速度慢(指數增加), 而是指TCP開始發送設置cwnd=1.
    2.思路:不要一開始就發送大量的數據, 先探測一下網絡的擁塞程度, 也就是說由小到大逐漸增長擁塞窗口的大小. 這裏用報文段的個數的擁塞窗口大小舉例說明慢開始算法, 實時擁塞窗口大小是以字節爲單位的
    3.爲了防止cwnd增加過大引發網絡擁塞, 設置一個慢開始門限(ssthresh狀態變量)
    (1)每收到一個ACK的回覆報文, 窗口就加1MSS 假設每一個報文被單獨確認. 發送方cwnd從1MSS開始, 發送1MSS後, 收到一個ACK. 此時cwnd=1+1=2;
    (2)而後發送方發送2MSS報文段, 會收到2個ACK, 此時cwnd=2+2=4.....依次類推擁塞窗口指數增長
    當cnwd<ssthresh, 使用慢開始算法
    當cnwd=ssthresh, 既可以使用慢開始算法, 也能夠使用擁塞避免算法
    當cnwd>ssthresh, 使用擁塞避免算法

  • 擁塞避免
    1.擁塞避免並不是徹底可以避免擁塞, 是說在擁塞避免階段將擁塞窗口控制爲按線性規律增加, 使網絡比較不容易出現擁塞.
    2.思路:讓擁塞窗口cwnd緩慢地增大, 即每通過一個往返時間RTT就把發送方的擁塞控制窗口加1.

擁塞發生: 當發生丟包進行數據包重傳時, 表示網絡已經擁塞. 分兩種狀況進行處理:
等到RTO超時, 重傳數據包: sshthresh=cwnd/2; cwnd 重置爲1; 進入慢啓動過程
在收到3個duplicate ACK時就開啓重傳, 進入快速恢復算法

  • 快重傳
  1. 快重傳要求接收方在收到一個失序的報文段後就當即發出重複確認(爲的是使發送方及早知道有報文段沒有到達對方)而不要等到本身發送數據時捎帶確認. 快重傳算法規定, 發送方只要一連收到三個重複確認就應當當即重傳對方還沒有收到的報文段, 而沒必要繼續等待設置的重傳計時器時間到期.
  2. 因爲不須要等待設置的重傳計時器到期, 能儘早重傳未被確認的報文段, 能提升整個網絡的吞吐量
  • 快恢復
    快速重傳和快速恢復算法通常同時使用。快速恢復算法是認爲,你還有3個Duplicated Acks說明網絡也不那麼糟糕,因此沒有必要像RTO超時那麼強烈,並不須要從新回到慢啓動進行,這樣可能下降效率。因此協議棧會作以下工做
  1. cwnd = cwnd/2
  2. sshthresh = cwnd
    而後啓動快速恢復算法
    1. 設置cwnd = ssthresh+ACK個數*MSS(通常狀況下會是3個dup ACK)
  3. 重傳丟失的數據包(對於重傳丟失的那個數據包
  4. 若是隻收到Dup ACK,那麼cwnd = cwnd + 1,而且在容許的條件下發送一個報文段, 若此後每一次接收到Dup ACK cwnd都會+1, 直至接收到新的ACK
  5. 若是收到新的ACK, 設置cwnd = ssthresh,進入擁塞避免階段
    快恢復增加過程

發送窗口增加到必定範圍時, 可能出現網絡空閒, 此時雙方不會接收到對等方的確認信息, 擁塞窗口要不斷的減少

AIMD算法:
乘法減少(Multiplicative Decrease):不論在慢開始階段或擁塞避免階段, 只要出現超時, 就把慢開始門限值減半(當前擁塞窗口的一半)
加法增大(Additive Increase):執行擁塞避免算法後, 使擁塞窗口緩慢增大, 以防止網絡過早出現擁塞
擁塞避免不能徹底避免擁塞, 只是在擁塞避免階段將擁塞窗口控制爲線性增加, 使網絡不容易出現擁塞

騰訊面試題

TCP的擁塞控制機制是什麼?請簡單說說.
答:咱們知道TCP經過一個定時器(timer)採樣了RTT並計算RTO, 可是, 若是網絡上的延時忽然增長, 那麼, TCP對這個事作出的應對只有重傳數據, 然而重傳會致使網絡的負擔更重, 因而會致使更大的延遲以及更多的丟包, 這就致使了惡性循環, 最終造成「網絡風暴」 —— TCP的擁塞控制機制就是用於應對這種狀況.
首先須要瞭解一個概念, 爲了在發送端調節所要發送的數據量, 定義了一個「擁塞窗口」(Congestion Window), 在發送數據時, 將擁塞窗口的大小與接收端ack的窗口大小作比較, 取較小者做爲發送數據量的上限.
擁塞控制主要是四個算法:

1.慢啓動:意思是剛剛加入網絡的鏈接, 一點一點地提速, 不要一上來就把路佔滿.
鏈接建好的開始先初始化cwnd = 1, 代表能夠傳一個MSS大小的數據.
每當收到一個ACK, cwnd++; 呈線性上升
每當過了一個RTT, cwnd = cwnd*2; 呈指數讓升
閾值ssthresh(slow start threshold), 是一個上限, 當cwnd >= ssthresh時, 就會進入「擁塞避免算法」

2.擁塞避免:當擁塞窗口 cwnd 達到一個閾值時, 窗口大小再也不呈指數上升, 而是以線性上升, 避免增加過快致使網絡擁塞.
每當收到一個ACK, cwnd = cwnd + 1/cwnd
每當過了一個RTT, cwnd = cwnd + 1

擁塞發生:當發生丟包進行數據包重傳時, 表示網絡已經擁塞. 分兩種狀況進行處理:

(1) 等到RTO超時, 重傳數據包: sshthresh=cwnd/2; cwnd 重置爲 1; 進入慢啓動過程

(2) 在收到3個duplicate ACK時就開啓重傳, 而不用等到RTO超時:sshthresh = cwnd; cwnd = cwnd /2;進入快速恢復算法

3.進入慢啓動過程
在收到3個duplicate ACK時就開啓重傳, 而不用等到RTO超時
sshthresh = cwnd = cwnd /2

進入快速恢復算法——Fast Recovery
4.快速恢復:至少收到了3個Duplicated Acks, 說明網絡也不那麼糟糕, 能夠快速恢復.
而後啓動快速恢復算法:
  cwnd = sshthresh + 3 * MSS (3的意思是確認有3個數據包被收到了)
  重傳Duplicated ACKs指定的數據包
  若是再收到duplicated Acks, 那麼cwnd = cwnd +1, 而且在容許的條件下發送一個報文段, 若此後每接收到一個duplicated Ack, cwnd++
  若是收到了新的Ack, 那麼, cwnd = sshthresh , 而後就進入了擁塞避免的算法了

死鎖

死鎖問題出現
  主機B的接收緩存滿了, rwnd=0. 主機A知道了就會暫停數據發送, 等待主機B的接收緩存有空閒. 若是此時主機B沒有數據發送給A那麼A將不可能知道主機B會有緩存空閒, 這會致使A被阻塞(主機B僅當他有數據發送或者有確認時纔會發送報文段給A)

解決死鎖問題
  當發送方A收到接收方B的窗口爲0的通知, 便啓動一個一個持續計數器, 每隔一段時間向B發送只有一個字節數據的零窗口探測報文段. 這些報文段將被接收方確認. 最終緩存將開始清空, 而且確認報文裏包含一個非0的rwnd值.

中止並等待ARQ與連續ARQ

中止等待協議
中止等待協議是tcp保證傳輸可靠的重要途徑,」中止等待」就是指發送完一個分組就中止發送,等待對方的確認,只有對方確認過,才發送下一個分組.

中止等待協議的優勢是簡單,可是缺點是信道的利用率過低,一次發送一條消息,使得信道的大部分時間內都是空閒的,爲了提升效率,咱們採用流水線傳輸,這就與下面兩個協議有關係了

二:連續ARQ協議和滑動窗口協議
這兩個協議主要解決的問題信道效率低和增大了吞吐量,以及控制流量的做用.

  • 連續ARQ協議:它是指發送方維護着一個窗口,這個窗口中不止一個分組,有好幾個分組,窗口的大小是由接收方返回的win值決定的,因此窗口的大小是動態變化的,只要在窗口中的分組均可以被髮送,這就使得TCP一次不是隻發送一個分組了,從而大大提升了信道的利用率.而且它採用累積確認的方式,對於按序到達的最後一個分組發送確認.

  • 滑動窗口協議:之因此叫滑動窗口協議,是由於窗口是不斷向前走的,該協議容許發送方在中止並等待確認前發送多個數據分組. 因爲發送方沒必要每發一個分組就停下來等待確認, 所以該協議能夠加速數據的傳輸,還能夠控制流量的問題.

  • 累積確認:若是發送方發送了5個分組,接收方只收到了1,2,4,5,沒有收到3分組,那麼個人確認信息只會說我指望下一個收到的分組是第三個,此時發送方會將3,4,5,所有重發一次,當通訊質量不是很好的時候,連續ARQ仍是會帶來負面影響.

TCP怎麼重傳

RTT(Round Trip Time): 一個鏈接的往返時間, 即數據發送時刻到接收到確認的時刻的差值
RTO(Retransmission Time Out): 重傳超時時間, 即從數據發送時刻算起, 超過這個時間便執行重傳

TCP模塊爲每一個TCP報文段都維護一個重傳定時器, 該定時器在TCP報文段第一次發送時啓動. 若是超時時間內未收到接收方的應答, TCP模塊將重傳TCP報文段並重置定時器. 在達到必定次數尚未成功時放棄併發送一個復位信號

這裏比較重要的是重傳超時時間, 怎樣設置這個定時器的時間(RTO), 從而保證對網絡資源最小的浪費. 由於若RTO過小, 可能有些報文只是遇到擁堵或網絡很差延遲較大而已, 這樣就會形成沒必要要的重傳. 太大的話, 使發送端須要等待過長的時間才能發現數據丟失, 影響網絡傳輸效率. 因爲不一樣的網絡狀況不同, 不可能設置同樣的RTO, 實際中RTO是根據網絡中的RTT(傳輸往返時間)來自適應調整的.

RTT和RTO的關係是: 因爲網絡波動的不肯定性, 每一個RTT都是動態變化的, 因此RTO也應隨着RTT動態變化
重傳定時器: 當TCP發送報文段時, 就建立這個特定報文段的重傳定時器, 若在定時器超時以前收到對報文段的確認, 則撤銷定時器; 若在收到對特定報文段的確認以前計時器超時, 則重傳該報文, 而且進行RTO=2*RTO進行退避

超時重傳有如下三種狀況:

  1. 分組丟失: 發送方發送分組, 接收方沒有收到分組, 那麼接收方不會發出確認, 只要發送方過一段時間沒有收到確認, 就認爲剛纔的分組丟了, 那麼發送方就會再次發送.
  2. 確認丟失: 發送方發送成功, 接收方接收成功, 確認分組也被髮送, 可是分組丟失, 那麼到了等待時間, 發送方沒有收到確認, 又會發送分組過去, 此時接收方前面已經收到了分組, 那麼此時接收方要作的事就是:丟棄分組,從新發送確認.
  3. 傳送延遲: 發送方發送成功, 接收方接收成功, 確認分組也被髮送, 沒有丟失, 可是因爲傳輸太慢, 等到了發送方設置的時間,發送方又會從新發送分組, 此時接收方要作的事情:丟棄分組,從新發送確認. 發送方若是收到兩個或者多個確認,就中止發送,丟棄其餘確認.

差錯恢復機制

假設主機A向主機B發送5個連續的報文段, 主機B對每一個報文段進行確認, 其中第二個報文段丟失, 其他報文段以及重傳的第二個報文段均被主機B正確接收, 主機A正確接收全部ACK報文段;報文段從1開始依次連續編號(即一、二、3……), 主機A的超時時間足夠長. 請回答下列問題:
1).若是分別採用GBN、SR和TCP協議, 則對應這三個協議, 主機A分別總共發了多少個報文段?主機B分別總共發送了多少個ACK?它們的序號是什麼?(針對3個協議分別給出解答)
2).若是對上述三個協議, 超時時間比5RTT長得多, 那麼哪一個協議將在最短的時間間隔內成功交付5個報文段?

(1)當採用GBN協議時, 由GBN協議可得:
主機A共發送了9個報文段, 首先發送報文段1,2,3,4,5, 當報文2丟失後, 重發報文段2,3,4,5共9個;
主機B共發送8個ACK, 首先發送ACK1, 報文段2丟失, 所以對於3,4,5都發送ACK1共4個ACK1, 後對於重傳的2,3,4,5, 則發送ACK2, ACK3, ACK4, ACK5, 一共8個ACK.

當採用SR協議時, 由SR協議可得:
主機A共發送了6個報文段, 首先發送報文段1,2,3,4,5, 當報文2丟失後, 重發報文段2共6個報文段;
主機B共發送5個ACK, 首先發送ACK1, ACK3, ACK4, ACK5, 對於重發的報文段2, 則發送ACK2共5個ACK.

當採用TCP協議時, 由TCP協議可得:
主機A共發送了6個報文段, 首先發送報文段1,2,3,4,5, 當報文2丟失後, 重發報文段2共6個報文段;
主機B共發送5個ACK, 首先發送4個ACK2, 重傳後發送一個ACK6一共5個ACK.
(2)採用TCP協議可在最短的時間間隔內成功交付5個報文段, 由於TCP有快速重傳機制, 即在未超時狀況下就開始重傳丟失的2號報文段.

GBN, SR, TCP

GBN: 失序後不緩存, 直接丟棄, 重傳失序後全部數據
SR: 緩存失序後數據, 只重傳失序的部分
TCP收到亂序數據後會將其放到亂序序列中, 而後發送重複ACK給對端. 對端若是收到多個重複的ACK, 認爲發生丟包, TCP會重傳最後確認的包開始的後續包. 這樣原先已經正確傳輸的包, 可能會重複發送, 下降了TCP性能.
爲改善這種狀況, 發展出SACK技術, 使用SACK選項能夠告知發包方收到了哪些數據, 發包方收到這些信息後就會知道哪些數據丟失, 而後當即重傳丟失的部分.
SACK --> 累計確認, 延遲確認(正常TCP斷開是4次揮手, 可是抓包抓到大部分都是3次揮手. 就是延遲確認的結果吧. )

TCP 爲何是可靠鏈接

tcp不可靠表如今:

  1. 差錯 --> 端到端校驗和
  2. 丟包 --> 超時重傳 + 確認機制
  3. 失序 --> tcp每一個段的第一個字節都有一個序號
  4. 重複 --> tcp對等方收到數據後會對數據進行重排, 經過序號機制
  5. 流量控制
  6. 擁塞控制

保證可靠性方法:

  1. 應用數據被分隔成TCP認爲最適合發送的數據塊, 稱爲段傳遞給IP層
  2. 當TCP發出一個段後, 它啓動一個定時器, 等待目的段確認收到這個報文段. 若是不能及時收到一個確認, 將從新發送這個報文段
  3. 當TCP收到發自TCP鏈接另外一端的數據, 它將發送一個確認. 這個確認不是當即發送, 一般推遲幾分之一秒
  4. TCP將保持它首部和數據的校驗和. 這是一個端到端的校驗和, 目的是檢測數據在傳輸過程當中的任何變化. 若是收到的校驗和有差錯, TCP將丟棄這個報文而且不確認(致使對方超時重傳)
  5. TCP承載與IP數據報來傳輸, 而IP數據報的到達可能會失序, 所以報文段的到達也可能失序. TCP將對收到的數據進行從新排序
  6. IP數據報會發生重複, TCP的接收端必須丟棄重複的數據
  7. TCP還能提供流量控制. TCP鏈接的每一方都有必定大小的緩衝空間

TCP與UDP中一個包的大小最大能多大

鏈路層MTU限制, 傳輸數據包超過MTU限制, 意味着在IP層會對數據包進行分片, MSS一般會取一個值保證不會超過MTU大小, 默認值536字節, 路由器MTU常常等於576, 576-20(IP頭部)-20(tcp頭部)=536, 傳輸的數據不超過536就不會致使IP層的分片, 因此上層的緩衝區不要超過536(避免IP層數據包的分組分片), 應用層緩衝區取512字節保證不會超過536字節(前提不去更改MSS的默認大小). 局域網MTU爲46~1500, 廣域網576


傳送門

數據校驗的意義

粘包處理

心跳意義

TCP新手誤區

UDP

UDP 是一個簡單的傳輸層協議. 和TCP相比, UDP有下面幾個顯著特性:

  1. UDP 缺少可靠性. UDP 自己不提供確認, 序列號, 超時重傳等機制. UDP 數據報可能在網絡中被複制, 被從新排序. 即 UDP 不保證數據報會到達其最終目的地, 也不保證各個數據報的前後順序, 也不保證每一個數據報只到達一次
  2. UDP 數據報是有長度的. 每一個UDP數據報都有長度, 若是一個數據報正確地到達目的地, 那麼該數據報的長度將隨數據一塊兒傳遞給接收方. 而 TCP 是一個字節流協議, 沒有任何(協議上的)記錄邊界.
  3. UDP 是無鏈接的. UDP 客戶和服務器以前沒必要存在長期的關係. UDP 發送數據報以前也不須要通過握手建立鏈接的過程.
  4. UDP 支持多播和廣播.

UDP 的主要應用場景

  • 須要資源少, 網絡狀況穩定的內網, 或者對於丟包不敏感的應用, 好比DHCP就是基於UDP協議的.
  • 不須要一對一溝通, 創建鏈接, 而是能夠廣播的應用. 由於它不面向鏈接, 因此能夠作到一對多, 承擔廣播或者多播的協議.
  • 須要處理速度快, 能夠容忍丟包, 可是即便網絡擁塞, 也絕不退縮, 勇往直前的時候

基於UDP的幾個例子:

  • 直播. 直播對實時性的要求比較高, 寧肯丟包, 也不要卡頓的, 因此不少直播應用都基於 UDP 實現了本身的視頻傳輸協議
  • 實時遊戲. 遊戲的特色也是實時性比較高, 在這種狀況下, 採用自定義的可靠的 UDP 協議, 自定義重傳策略, 可以把產生的延遲降到最低, 減小網絡問題對遊戲形成的影響
  • 物聯網. 一方面, 物聯網領域中斷資源少, 極可能知識個很小的嵌入式系統, 而維護 TCP 協議的代價太大了;另外一方面, 物聯網對實時性的要求也特別高. 好比 Google 旗下的 Nest 簡歷 Thread Group, 推出了物聯網通訊協議 Thread, 就是基於 UDP 協議的

TCP 和 UDP 的區別

  1. TCP 是面向鏈接的, UDP 是面向無鏈接的
  2. TCP 是面向字節流的, UDP 是基於數據報的
  3. TCP 保證數據正確性, UDP 可能丟包
  4. TCP 保證數據順序, UDP 不保證
    UDP程序結構較簡單

QQ通信

無論UDP仍是TCP,最終登錄成功以後,QQ都會有一個TCP鏈接來保持在線狀態。這個TCP鏈接的遠程端口通常是80,採用UDP方式登錄的時候,端口是8000。

DDOS

DDoS是分佈式拒絕服務(distributeddenial-of-service)的簡稱. DDoS攻擊是指攻擊者經過控制傀儡計算機, 大量持續地向攻擊目標請求資源, 從而阻塞了正經常使用戶的合法請求. DDoS攻擊的對象主要是網站和域名(DNS)服務器, 大量消耗服務器的資源, 包括內存、CPU以及網絡帶寬等, 使其不能提供正常服務. 此外, DDoS也能夠對網絡基礎設施進行攻擊, 如路由器、交換機等, 經過巨大的攻擊流量, 能夠致使目標所在的網絡性能大幅降低甚至癱瘓, 使目標主機不能對外提供服務.

syn flood

傳送門

使用syncookie的基本原理是:在第二次TCP握手的時候, Server端不爲鏈接分配資源, Server端返回了帶syncookies的syn,ack包, 在第三次握手的時候client端也要帶上這個syncookie, 此時Server端才正式分配系統資源.

第一種是縮短SYN Timeout時間: 因爲SYN Flood攻擊的效果取決於服務器上保持的SYN半鏈接數, 這個值=SYN攻擊的頻度 x SYN Timeout, 因此經過縮短從接收到SYN報文到肯定這個報文無效並丟棄該鏈接的時間, 例如設置爲20秒如下(太低的SYN Timeout設置可能會影響客戶的正常訪問), 能夠成倍的下降服務器的負荷.
第二種方法是設置SYN Cookie: 就是給每個請求鏈接的IP地址分配一個Cookie, 若是短期內連續受到某個IP的重複SYN報文, 就認定是受到了攻擊, 之後從這個IP地址來的包會被丟棄.

UDP flood

限流, 指紋學習
傳送門

DNS flood

令使用udp訪問dns服務器的客戶端使用tcp從新訪問
傳送門

HTTP flood/CC flood

利用http報文重定向機制
傳送門

你所知道的套接字選項

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);  
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); 
// level指定控制套接字的層次.能夠取三種值:
// 1)SOL_SOCKET:通用套接字選項.
// 2)IPPROTO_IP:IP選項.
// 3)IPPROTO_TCP:TCP選項. 

SOL_SOCKET

得到套接字錯誤

若是fd上出現了錯誤, 那麼第一次調用getsockopt會經過status返回錯誤緣由. 若是此時並無調用close(fd), 按理說這個錯誤在fd上依然存在, 可是若是再次調用上面的getsockopt, 則會告知用戶此fd上沒有任何錯誤. 這種狀況常常會發生在函數之間傳遞fd時, 一個函數A裏面作了getsockopt判斷, 以後將fd傳至別的函數B, 函數B不知道fd的狀態, 再次調用getsockopt, 會誤認爲fd上沒有錯誤了.
因此若是在fd上沒有任何讀寫操做的話, fd上的getsockopt要只調用一次, 以後, 要將該次getsockopt的狀態和fd一塊兒傳遞給別的函數. 省得出現上面的問題.

int optval;
socklen_t optlen = sizeof optval;
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
    return errno;
}
else {
    return optval;
}

SO_REUSEADDR

容許重用本地地址和端口

int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

SO_KEEPALIVE

進行長鏈接通訊時(如移動應用開發中手機端與服務端要維持一個穩定的鏈接, 進行實時消息傳遞), 須要在應用層定義心跳機制. 有人問, TCP自己就是可靠傳輸協議, 爲何還須要應用層來實現心跳機制呢?
【然而】在使用TCP創建鏈接後
1) 不少防火牆等對於空閒socket自動關閉
2) 對於非正常斷開, 服務器並不能檢測到(如拔網線, 直接關機等).

【解決辦法--keepalive屬性】
套接字自己是有一套心跳保活機制的, 不過默認的設置並不像咱們一廂情願的那樣有效. 在雙方TCP套接字創建鏈接後(即都進入ESTABLISHED狀態)而且在兩個小時左右上層沒有任何數據傳輸的狀況下, 這套機制纔會被激活.

不少人認爲兩個小時的時間設置得很不合理. 爲何不設置成爲10分鐘, 或者更短的時間?(能夠經過SO_KEEPALIVE選項設置. )可是這樣作其實並不被推薦. 實際上這套機制只是操做系統底層使用的一個被動機制, 原理上不該該被上層應用層使用. 當系統關閉一個由KEEPALIVE機制檢查出來的死鏈接時, 是不會主動通知上層應用的, 只有在調用相應的IO操做在返回值中檢查出來
所以, 忘記SO_KEEPALIVE, 在應用層本身寫一套保活機制比較靠譜.

int optval = on ? 1 : 0;
setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);

SO_RCVTIMEO和SO_SNDTIMEO

int nNetTimeout=1000;//1秒
//發送時限
setsockopt(socket, SOL_S0CKET,SO_SNDTIMEO, (char *)&nNetTimeout,sizeof(int));
//接收時限
setsockopt(socket, SOL_S0CKET,SO_RCVTIMEO, (char *)&nNetTimeout,sizeof(int));

在TCP鏈接中, recv等函數默認爲阻塞模式(block), 即直到有數據到來以前函數不會返回, 而咱們有時則須要一種超時機制使其在必定時間後返回而無論是否有數據到來, 這裏咱們就會用到setsockopt()函數:

int  setsockopt(int  s, int level, int optname, void* optval, socklen_t* optlen);
這裏咱們要涉及到一個結構:
struct timeval
{
        time_t tv_sec;
        time_t tv_usec;
};
這裏第一個域的單位爲秒, 第二個域的單位爲微秒. 
struct timeval tv_out;
tv_out.tv_sec = 1;
tv_out.tv_usec = 0;
填充這個結構後, 咱們就能夠以以下的方式調用這個函數:
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));(具體參數能夠man一下, 或查看MSDN)
這樣咱們就設定了recv()函數的超時機制, 當超過tv_out設定的時間而沒有數據到來時recv()就會返回0值.

SO_RCVBUF和SO_SNDBUF

SO_RCVBUF和SO_SNDBUF每一個套接口都有一個發送緩衝區和一個接收緩衝區, 使用這兩個套接口選項能夠改變缺省緩衝區大小. 默認8K

// 接收緩衝區
int nRecvBuf=32*1024;         //設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發送緩衝區
int nSendBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

注意: 當設置TCP套接口接收緩衝區的大小時, 函數調用順序是很重要的, 由於TCP的窗口規模選項是在創建鏈接時用SYN與對方互換獲得的. 對於客戶, O_RCVBUF選項必須在connect以前設置;對於服務器, SO_RCVBUF選項必須在listen前設置.

TCP_NODELAY

Nagle算法能夠必定程度上避免網絡擁塞, Nagle算法: 若頻發送小數據包, 會作些延遲, 等待後續數據包合併一塊兒發生
TCP_NODELAY選項能夠禁用Nagle算法. 禁用Nagle算法, 能夠避免連續發包出現延遲(通常是200ms), 這對於編寫低延遲的網絡服務很重要

int optval = on ? 1 : 0;        // 1: 禁用, 0: 不由用
::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);

SIGPIPE

默認狀況下, 往一個讀端關閉的管道或socket鏈接中寫數據將引起SIGPIPE信號. 須要在代碼中捕獲處理該信號, 或者至少忽略它, 由於程序接收到SIGPIPE信號的默認行爲是結束進程, 而絕對不但願由於錯誤的寫操做而致使程序的退出. 引發SIGPIPE信號的寫操做將設置errno

向一個已經收到FIN的套接字中寫是容許的, 接收到FIN僅僅表明對方再也不發送數據, 它並不能確認對方的進程是否已經消失. 若對方進程消失, 這時須要客戶端調用write, 此次調用並不會產生斷開的管道, 會發現對等方的進程不存在了, 對等方的TCP協議棧會發送RST鏈接重置的TCP協議段過來, 這意味着對等方讀管道不存在

在收到RST報文段以後, 若是再調用write就會產生SIGPIPE信號, 對於這個信號的處理方式默認退出進程, 一般改變默認處理方式, 使用signal(SIGPIPE, SIG_IGN);

void echo_clie(int sock) {
    char sendbuf[1024] = { 0 };
    char recvbuf[1024] = { 0 };
    while (fget(sendbuf, sizeof(sendbuf), stdin) != NULL) { // 若此時服務器端異常終止, 客戶端沒有及時read此時的FIN報文段
        write(sock, sendbuf, 1);                            // 向已經終止的進程發送數據會返回RST
        write(sock, sendbuf + 1, strlen(sendbuf) - 1);      // 向返回RST的套接字繼續發送數據會產生SIGPIPE信號
        
        int ret = readline(sock, recvbuf, sizeof(recvbuf)); // 返回FIN, RST, SIGPIPE
        //...
    }
}

套接字編程

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int close(int fd);

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

// 得到本地/對等端地址及端口
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 地址轉換
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
int inet_aton(const char *cp, struct in_addr *inp);

// 兩個宏用於定義地址轉換後字符串的長度
#define INET_ADDRSTRLEN 16  /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */

// 字節序轉換
uint16_t htobe16(uint16_t host_16bits);
uint32_t htobe32(uint32_t host_32bits);
uint64_t htobe64(uint64_t host_64bits);
uint16_t be16toh(uint16_t big_endian_16bits);
uint32_t be32toh(uint32_t big_endian_32bits);
uint64_t be64toh(uint64_t big_endian_64bits);
// 下面轉換函數沒有上面強
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

// Linux 2.6.27以上的內核支持SOCK_NONBLOCK與SOCK_CLOEXEC
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);

int flags = ::fcntl(sockfd, F_GETFL, 0);
flags |= O_NONBLOCK;
int ret = ::fcntl(sockfd, F_SETFL, flags);

// close-on-exec
flags = ::fcntl(sockfd, F_GETFD, 0);
flags |= FD_CLOEXEC;
ret = ::fcntl(sockfd, F_SETFD, flags);

HTTP

參考
參考

你是怎麼理解http

  1. 目前最成功的互聯網協議之一.
  2. 基於TCP協議的一個報文協議, 其報文頭是不定長且任意擴展的, 這也使得這個協議充滿了生命力.
  3. 設計很是簡單、輕巧, 卻又功能強大.
  4. 沒有人能完整描述HTTP協議, 由於這個協議的細節能夠隨時擴充. 例如控制咖啡壺什麼的……

HTTP1.0 HTTP1.1

HTTP1.0: 無狀態, 無鏈接. 無狀態性能夠藉助cookie/session機制來作身份認證和狀態記錄
問題: 沒法複用鏈接, 隊頭阻塞
隊頭阻塞(head of line blocking). 因爲HTTP1.0規定下一個請求必須在前一個請求響應到達以前才能發送. 假設前一個請求響應一直不到達, 那麼下一個請求就不發送, 一樣的後面的請求也給阻塞了.

HTTP1.1

  1. 緩存處理, 在HTTP1.0中主要使用header裏的If-Modified-Since, Expires來作爲緩存判斷的標準, HTTP1.1則引入了更多的緩存控制策略例如Entity tag, If-Unmodified-Since, If-Match, If-None-Match等更多可供選擇的緩存頭來控制緩存策略

  2. 帶寬優化及網絡鏈接的使用, HTTP1.0中, 存在一些浪費帶寬的現象, 例如客戶端只是須要某個對象的一部分, 而服務器卻將整個對象送過來了, 而且不支持斷點續傳功能, HTTP1.1則在請求頭引入了Range頭域, 它容許只請求資源的某個部分, 即返回碼是206(Partial Content), 這樣就方便了開發者自由的選擇以便於充分利用帶寬和鏈接.

  3. 錯誤通知的管理, 在HTTP1.1中新增了24個錯誤狀態響應碼, 如409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示服務器上的某個資源被永久性的刪除.

  4. Host頭處理, 在HTTP1.0中認爲每臺服務器都綁定一個惟一的IP地址, 所以, 請求消息中的URL並無傳遞主機名(hostname). 但隨着虛擬主機技術的發展, 在一臺物理服務器上能夠存在多個虛擬主機(Multi-homed Web Servers), 而且它們共享一個IP地址. HTTP1.1的請求消息和響應消息都應支持Host頭域, 且請求消息中若是沒有Host頭域會報告一個錯誤(400 Bad Request).

  5. 長鏈接, HTTP 1.1支持長鏈接(PersistentConnection)在HTTP1.1中默認開啓Connection: keep-alive, 必定程度上彌補了HTTP1.0每次請求都要建立鏈接的缺點.

  6. 請求的流水線處理, 基於HTTP1.1的長鏈接, 使得請求管線化成爲可能. 管線化使得請求可以「並行」傳輸. 舉個例子來講, 假如響應的主體是一個html頁面, 頁面中包含了不少img, 這個時候keep-alive就起了很大的做用, 可以進行「並行」發送多個請求. (注意這裏的「並行」並非真正意義上的並行傳輸, 具體解釋以下.)須要注意的是, 服務器必須按照客戶端請求的前後順序依次回送相應的結果, 以保證客戶端可以區分出每次請求的響應內容, 一旦有某請求超時等, 後續請求只能被阻塞. 也就是說, HTTP管道化可讓咱們把先進先出隊列從客戶端(請求隊列)遷移到服務端(響應隊列).

    如: 客戶端同時發了兩個請求分別來獲取html和css, 假如說服務器的css資源先準備就緒, 服務器也會先發送html再發送css. 換句話來講, 只有等到html響應的資源徹底傳輸完畢後, css響應的資源才能開始傳輸. 也就是說, 不容許同時存在兩個並行的響應.

缺點:雖然容許複用TCP鏈接, 可是同一個TCP鏈接裏面, 全部的數據通訊是按次序進行的. 服務器只有處理完一個請求, 纔會接着處理下一個請求. 若是前面的處理特別慢, 後面就會有許多請求排隊等着. 這將致使「隊頭堵塞」
避免方式:一是減小請求數, 二是同時多開持久鏈接

HTTP1.0 定義了三種請求方法: GET, POST 和 HEAD方法.
HTTP1.1 新增了六種請求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法.

HTTP1.0和1.1現存的一些問題

HTTP/1.1 缺點

  1. HTTP/1.0一次只容許在一個TCP鏈接上發起一個請求,HTTP/1.1使用的流水線技術也只能部分處理請求併發,仍然會存在隊列頭阻塞問題,所以客戶端在須要發起屢次請求時,一般會採用創建多鏈接來減小延遲。
  2. 單向請求,只能由客戶端發起。
  3. 請求報文與響應報文首部信息冗餘量大。
  4. 數據未壓縮,致使數據的傳輸量大。

SPDY

SPDY是Speedy的暱音,意爲「更快」。它是Google開發的基於TCP協議的應用層協議。目標是優化HTTP協議的性能,經過壓縮、多路複用和優先級等技術,縮短網頁的加載時間並提升安全性。SPDY協議的核心思想是儘可能減小TCP鏈接數。SPDY並非一種用於替代HTTP的協議,而是對HTTP協議的加強。

SPDY能夠說是綜合了HTTPS和HTTP二者有點於一體的傳輸協議, 主要解決:

  1. 多路複用. 多路複用經過多個請求stream共享一個tcp鏈接的方式, 解決了HOL blocking的問題, 下降了延遲同時提升了帶寬的利用率.
  2. 請求優先級. 多路複用帶來一個新的問題是, 在鏈接共享的基礎之上有可能會致使關鍵請求被阻塞. SPDY容許給每一個request設置優先級, 這樣重要的請求就會優先獲得響應. 好比瀏覽器加載首頁, 首頁的html內容應該優先展現, 以後纔是各類靜態資源文件, 腳本文件等加載, 這樣能夠保證用戶能第一時間看到網頁內容.
  3. header壓縮. 前面提到HTTP1.x的header不少時候都是重複多餘的. 選擇合適的壓縮算法能夠減少包的大小和數量.
  4. 基於HTTPS的加密協議傳輸, 大大提升了傳輸數據的可靠性.
  5. 服務端推送, 採用了SPDY的網頁, 例如個人網頁有一個sytle.css的請求, 在客戶端收到sytle.css數據的同時, 服務端會將sytle.js的文件推送給客戶端, 當客戶端再次嘗試獲取sytle.js時就能夠直接從緩存中獲取到, 不用再發請求了. SPDY構成圖:

HTTP2.0

HTTP/2是HTTP協議自1999年HTTP1.1發佈後的首個更新,主要基於SPDY協議。HTTP2.0的特色是:在不改動HTTP語義、方法、狀態碼、URI及首部字段的狀況下,大幅度提升了web性能。

HTTP2.0能夠說是SPDY的升級版(其實本來也是基於SPDY設計的), 可是, HTTP2.0 跟 SPDY 仍有不一樣的地方, 主要是如下兩點:

  • HTTP2.0 支持明文HTTP傳輸, 而SPDY強制使用HTTPS
  • HTTP2.0 消息頭的壓縮算法採用HPACK, 而非SPDY採用的 DEFLATE
  1. 二進制分幀
  • HTTP/1.1 版的頭信息確定是文本(ASCII編碼), 數據體能夠是文本, 也能夠是二進制. HTTP/2 則是一個完全的二進制協議, 頭信息和數據體都是二進制, 而且統稱爲」幀」:頭信息幀和數據幀.
  • 二進制協議解析起來更高效、「線上」更緊湊, 更重要只有0和1的組合錯誤更少.

[][https://image-static.segmentfault.com/293/521/293521278-5a6e83af997f9_articlex]

  1. 徹底多路複用, 而非有序並阻塞的、只需一個鏈接便可實現並行;
  • 流(stream):已創建鏈接上的雙向字節流.
  • 消息:與邏輯消息對應的完整的一系列數據幀.
  • 幀(frame):HTTP2.0通訊的最小單位, 每一個幀包含幀頭部, 至少也會標識出當前幀所屬的流(stream id).

    從圖中可見, 全部的HTTP2.0通訊都在一個TCP鏈接上完成, 這個鏈接能夠承載任意數量的雙向數據流.
    每一個數據流以消息的形式發送, 而消息由一或多個幀組成. 這些幀能夠亂序發送, 而後再根據每一個幀頭部的流標識符(stream id)從新組裝.
    舉個例子, 每一個請求是一個數據流, 數據流以消息的方式發送, 而消息又分爲多個幀, 幀頭部記錄着stream id用來標識所屬的數據流, 不一樣屬的幀能夠在鏈接中隨機混雜在一塊兒. 接收方能夠根據stream id將幀再歸屬到各自不一樣的請求當中去.
    另外, 多路複用(鏈接共享)可能會致使關鍵請求被阻塞. HTTP2.0裏每一個數據流均可以設置優先級和依賴, 優先級高的數據流會被服務器優先處理和返回給客戶端, 數據流還能夠依賴其餘的子數據流.
    可見, HTTP2.0實現了真正的並行傳輸, 它可以在一個TCP上進行任意數量HTTP請求. 而這個強大的功能則是基於「二進制分幀」的特性.
  1. 使用報頭壓縮
  • HTTP 協議是沒有狀態, 致使每次請求都必須附上全部信息. 因此, 請求的不少頭字段都是重複的, 好比說cookie, 默認狀況下, 瀏覽器會在每次請求的時候, 把cookie附在header上面發送給服務器. (因爲cookie比較大且每次都重複發送, 通常不存儲信息, 只是用來作狀態記錄和身份認證)
  • HTTP2.0使用頭信息壓縮機制來減小須要傳輸的header大小, 通信雙方各自cache一份header fields表, 全部字段都會存入這個表, 產生一個索引號, 以後就不發送一樣字段了, 只需發送索引號. 對於相同的頭部, 沒必要再經過請求發送, 只需發送一次;既避免了重複header的傳輸, 又減少了須要傳輸的大小. 高效的壓縮算法能夠很大的壓縮header, 減小發送包的數量從而下降延遲.
  1. 服務器推送
  • HTTP/2 容許服務器未經請求, 主動向客戶端發送資源;
  • 經過推送那些服務器任務客戶端將會須要的內容到客戶端的緩存中, 避免往返的延遲
  1. 更安全
    HTTP2.0使用了tls的拓展ALPN作爲協議升級,除此以外,HTTP2.0對tls的安全性作了近一步增強,經過黑名單機制禁用了幾百種再也不安全的加密算法。

HTTP2.0其實能夠支持非HTTPS的, 可是如今主流的瀏覽器像chrome, firefox表示仍是隻支持基於 TLS 部署的HTTP2.0協議, 因此要想升級成HTTP2.0仍是先升級HTTPS爲好.

HTTP1.1的合併請求是否適用於HTTP2.0

就是http1.1的請求的流水線處理是否適用於http2.0
首先, 答案是「沒有必要」. 之因此沒有必要, 是由於這跟HTTP2.0的頭部壓縮有很大的關係.
在頭部壓縮技術中, 客戶端和服務器均會維護兩份相同的靜態字典動態字典.

  • 靜態字典中, 包含了常見的頭部名稱以及頭部名稱與值的組合. 靜態字典在首次請求時就能夠使用. 那麼如今頭部的字段就能夠被簡寫成靜態字典中相應字段對應的index.
  • 動態字典跟鏈接的上下文相關, 每一個HTTP/2鏈接維護的動態字典是不盡相同的. 動態字典能夠在鏈接中不停的進行更新.

也就是說, 本來完整的HTTP報文頭部的鍵值對或字段, 因爲字典的存在, 如今能夠轉換成索引index, 在相應的端再進行查找還原, 也就起到了壓縮的做用.
因此, 同一個鏈接上產生的請求和響應越多, 動態字典累積得越全, 頭部壓縮的效果也就越好, 因此針對HTTP/2網站, 最佳實踐是不要合併資源.
另外, HTTP2.0多路複用使得請求能夠並行傳輸, 而HTTP1.1合併請求的一個緣由也是爲了防止過多的HTTP請求帶來的阻塞問題. 而如今HTTP2.0已經可以並行傳輸了, 因此合併請求也就沒有必要了.

QUIC

這裏額外給你們介紹一個協議, 是由Google基於UDP實現的同爲傳輸層的協議, 目標是但願替代TCP協議.
該協議支持多路複用, 雖說HTTP2.0也支持多路複用, 可是下層仍然是TCP, 由於TCP的重傳機制, 只要一個包丟失就得判斷丟包而且重傳, 致使發生隊頭阻塞的問題, 可是UDP沒有這個限制. 除此以外, 它還有以下特色:

  1. 實現了本身的加密協議, 經過相似TCP的TFO機制實現0-RTT, 固然TLS1.3已經實現了0-RTT.
  2. 支持重傳和糾錯機制, 在只丟失一個包的狀況下不須要重傳, 使用糾錯機制恢復丟失的包.
    糾錯機制:經過異或的方式, 算出發出去的數據的異或值並單獨發出一個包, 服務端在發現有一個包丟失的狀況下, 經過其餘數據包的異或值包算出丟失包.
    在丟失兩個包及以上的狀況就是用重傳機制, 由於算不出來了.

由http升級爲https須要作哪些操做

1、獲取證書
升級到 HTTPS 協議的第一步,就是要得到一張證書。
證書是一個二進制文件,裏面包含通過認證的網站公鑰和一些元數據,要從經銷商購買。
2、安裝證書
證書能夠放在/etc/ssl目錄(Linux 系統),而後根據你使用的Web服務器進行配置。
3、修改連接
下一步,網頁加載的 HTTP 資源,要所有改爲 HTTPS 連接。由於加密網頁內若是有非加密的資源,瀏覽器是不會加載那些資源的。
4、301重定向
下一步,修改 Web 服務器的配置文件,使用 301 重定向,將 HTTP 協議的訪問導向 HTTPS 協議。

HTTPS

HTTP協議一般承載於TCP協議之上, 在HTTP和TCP之間添加一個安全協議層(SSL或TSL), 這個時候, 就成了咱們常說的HTTPS.

HTTPS主要做用

  • (1)對數據進行加密, 並創建一個信息安全通道, 來保證傳輸過程當中的數據安全;
  • (2)對網站服務器進行真實身份認證.

HTTPS和HTTP的區別

  • 一、HTTPS是加密傳輸協議, HTTP是明文傳輸協議;
  • 二、HTTPS須要用到SSL證書, 而HTTP不用;
  • 三、HTTPS比HTTP更加安全, 對搜索引擎更友好, 利於SEO,
  • 四、HTTPS標準端口443, HTTP標準端口80;
  • 五、HTTPS基於傳輸層, HTTP基於應用層;

HTTPS和HTTP的工做過程區別

  1. HTTP 包含動做:
      瀏覽器打開一個 TCP 鏈接
      瀏覽器發送 HTTP 請求到服務器端
      服務器發送 HTTP 迴應信息到瀏覽器
      TCP 鏈接關閉
  2. SSL 包含動做:
      驗證服務器端
      客戶端和服務器端選擇加密算法和密碼, 確保雙方都支持
      驗證客戶端(可選)
      使用公鑰加密技術來生成共享加密數據
      建立一個加密的 SSL 鏈接
      基於該 SSL 鏈接傳遞 HTTP 請求

HTTPS加密方式

  1. 對稱加密:加密和解密都是使用的同一個密鑰;
  2. 非對稱加密:
      加密使用的密鑰和解密使用的密鑰是不相同的, 分別稱爲:公鑰、私鑰;
      公鑰和算法都是公開的, 私鑰是保密的.
      非對稱加密過程:
        服務端生成配對的公鑰和私鑰
        私鑰保存在服務端, 公鑰發送給客戶端
        客戶端使用公鑰加密明文傳輸給服務端
        服務端使用私鑰解密密文獲得明文
  3. 數字簽名:簽名就是在信息的後面再加上一段內容, 能夠證實信息沒有被修改過.

HTTPS的優勢
儘管HTTPS並不是絕對安全, 掌握根證書的機構、掌握加密算法的組織一樣能夠進行中間人形式的攻擊, 但HTTPS還是現行架構下最安全的解決方案, 主要有如下幾個好處:
(1)使用HTTPS協議可認證用戶和服務器, 確保數據發送到正確的客戶機和服務器;
(2)HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議, 要比http協議安全, 可防止數據在傳輸過程當中不被竊取、改變, 確保數據的完整性.
(3)HTTPS是現行架構下最安全的解決方案, 雖然不是絕對安全, 但它大幅增長了中間人攻擊的成本.
(4)谷歌曾在2014年8月份調整搜索引擎算法, 並稱「比起同等HTTP網站, 採用HTTPS加密的網站在搜索結果中的排名將會更高」.

HTTPS的缺點
(1)HTTPS協議握手階段比較費時, 會使頁面的加載時間延長近50%, 增長10%到20%的耗電;
(2)HTTPS鏈接緩存不如HTTP高效, 會增長數據開銷和功耗, 甚至已有的安全措施也會所以而受到影響;
(3)SSL證書須要錢, 功能越強大的證書費用越高, 我的網站、小網站沒有必要通常不會用.
(4)SSL證書一般須要綁定IP, 不能在同一IP上綁定多個域名, IPv4資源不可能支撐這個消耗.
(5)HTTPS協議的加密範圍也比較有限, 在黑客攻擊、拒絕服務攻擊、服務器劫持等方面幾乎起不到什麼做用. 最關鍵的, SSL證書的信用鏈體系並不安全, 特別是在某些國家能夠控制CA根證書的狀況下, 中間人攻擊同樣可行.

加密
HTTPS

SSL創建的過程

  1. 客戶端經過發送Client Hello報文開始SSL通訊。報文中包含客戶端支持的SSL的指定版本、加密組件(Cipher Suite)列表(所使用的加密算法及密鑰長度等)。
    注意:客戶端還會附加一個隨機數,這裏記爲A。
  2. 服務器可進行SSL通訊時,會以Server Hello報文做爲應答。和客戶端同樣,在報文中包含SSL版本以及加密組件。服務器的加密組件內容是從接收到的客戶端加密組件內篩選出來的。
    注意:這裏服務器一樣會附加一個隨機數,發給客戶端,這裏記爲B。
  3. 以後服務器發送Certificate報文。報文中包含公開密鑰證書。(具體的數字簽名請看證書一節)
  4. 最後服務器發送Server Hello Done報文通知客戶端,最初階段的SSL握手協商部分結束。
    5SSL第一次握手結束後,客戶端會對服務器發過來的證書進行驗證,若是驗證成功,解密取出證書中的公鑰。(具體查看證書一節)
    接着,客戶端以Client Key Exchange報文做爲迴應。報文中包含通訊加密中使用的一種被稱爲Pre-master secret的隨機密碼串。該報文使用從證書中解密得到的公鑰進行加密(其實就是服務器的公鑰)。
  5. 客戶端繼續發送Change Cipher Spec報文。用於告知服務端,客戶端已經切換到以前協商好的加密套件(Cipher Suite)的狀態,準備使用以前協商好的加密套件加密數據並傳輸了。
  6. 客戶端發送Finished報文。該報文包含鏈接至今所有報文的總體校驗值(也就是HASH值),用來供服務器校驗。
  7. 服務器接收到客戶端的請求以後,使用私鑰解密報文,把Pre-master secret取出來。接着,服務器一樣發送Change Cipher Spec報文。
  8. 服務器一樣發送Finished報文,用來供客戶端校驗。
  9. 服務器和客戶端的Finished報文交換完畢以後,SSL鏈接就算創建完成。固然,通訊會受到SSL的保護。今後處開始進行應用層協議的通訊,即發送HTTP請求。
  10. 應用層協議通訊,即發送HTTP響應。
  11. 最後由客戶端斷開鏈接。斷開鏈接時,發送close_notify報文。上圖作了一些省略,這步以後再發送TCP FIN報文來關閉與TCP的通訊。

整個過程當中產生的三個隨機數有什麼用呢?還有,後面進行HTTP通訊的時候,是用哪個密鑰進行加密,還有怎麼保證報文的完整性。

對於客戶端:
當其生成了Pre-master secret以後,會結合原來的A、B隨機數,用DH算法計算出一個master secret,緊接着根據這個master secret推導出hash secret和session secret。

對於服務端:
當其解密得到了Pre-master secret以後,會結合原來的A、B隨機數,用DH算法計算出一個master secret,緊接着根據這個master secret推導出hash secret和session secret。

在客戶端和服務端的master secret是依據三個隨機數推導出來的,它是不會在網絡上傳輸的,只有雙方知道,不會有第三者知道。同時,客戶端推導出來的session secret和hash secret與服務端也是徹底同樣的

那麼如今雙方若是開始使用對稱算法加密來進行通信,使用哪一個做爲共享的密鑰呢?過程是這樣子的:

  • 雙方使用對稱加密算法進行加密,用hash secret對HTTP報文作一次運算生成一個MAC,附在HTTP報文的後面, 並用私鑰進行加密MAC,而後用session-secret加密全部數據(HTTP+MAC),而後發送。
  • 接收方則先用session-secret解密數據,而後獲得HTTP+加密後的MAC, 用公鑰解密MAC,再用相同的算法計算出本身的MAC,若是兩個MAC相等,證實數據沒有被篡改。

    MAC(Message Authentication Code)稱爲報文摘要,可以查知報文是否遭到篡改,從而保護報文的完整性。

數字簽名&數字證書

信息安全中有三個須要解決的問題:

  1. 保密性(Confidentiality):信息在傳輸時不被泄露
  2. 完整性(Integrity):信息在傳輸時不被篡改
  3. 有效性(Availability):信息的使用者是合法的

這三要素統稱爲CIA Triad。公鑰密碼解決保密性問題; 數字簽名解決完整性問題和有效性問題

數字簽名: 信息進行哈希計算取得哈希值, 而後用服務器的私鑰進行加密

生成簽名, 通常來講,不直接對消息進行簽名,而是對消息的哈希值進行簽名,步驟以下。

  1. 對消息進行哈希計算,獲得哈希值
  2. 利用私鑰對哈希值進行加密,生成簽名
  3. 將簽名附加在消息後面,一塊兒發送過去

驗證簽名

  1. 收到消息後,提取消息中的簽名
  2. 用公鑰對簽名進行解密,獲得哈希值1。
  3. 對消息中的正文進行哈希計算,獲得哈希值2。
  4. 比較哈希值1和哈希值2,若是相同,則驗證成功。

數字證書: 對服務器的公鑰進行哈希計算取得哈希值, 使用CA的私鑰進行加密. 證書實際上就是對公鑰進行數字簽名,它是對公鑰合法性提供證實的技術。

如何生成證書?

  1. 服務器將公鑰A給CA(公鑰是服務器的)
  2. CA用本身的私鑰B給公鑰A加密,生成數字簽名A
  3. CA把公鑰A,數字簽名A,附加一些服務器信息整合在一塊兒,生成證書,發回給服務器。

如何驗證證書?

  1. 客戶端獲得證書
  2. 客戶端獲得證書的公鑰B(經過CA或其它途徑)
  3. 客戶端用公鑰B對證書中的數字簽名解密,獲得哈希值
  4. 客戶端對公鑰進行哈希值計算
  5. 兩個哈希值對比,若是相同,則證書合法。

什麼是數字簽名和證書?

對稱加密的不足主要有兩點

  1. 發送方和接收方首先須要共享相同的密鑰,即存在密鑰k的分發問題,如何安全的把共享密鑰在雙方進行分享,這自己也是一個如何安全通訊的問題,一種方法是提早雙方約定好,不經過具體的通訊進行協商,避免被監聽和截獲。另一種方式,將是下面咱們介紹的經過非對稱加密信道進行對稱密碼的分發和共享,即混合加密系統。
  2. 密鑰管理的複雜度問題。因爲對稱加密的密鑰是一對一的使用方式,若一方要跟n方通訊,則須要維護n對密鑰。

對稱加密的好處是:加密和解密的速度要比非對稱加密快不少,所以經常使用非對稱加密創建的安全信道進行共享密鑰的分享,完成後,具體的加解密則使用對稱加密。即混合加密系統。

從輸入 URL 到頁面加載完成, 中間發生了什麼

  1. URL輸入: 主要組成部分有: protocol(協議), hostname(主機名), port(端口號)
  2. DNS解析: 查找順序: 瀏覽器緩存--> 操做系統緩存--> 本地host文件 --> 路由器緩存--> ISP DNS緩存 --> 頂級DNS服務器/根DNS服務器, 有迭代查詢, 遞歸查詢兩種方式
  3. TCP鏈接: 鏈接涉及三次握手
  4. 發送HTTP請求: 請求行, 請求頭, 空行, 請求正文
    GET/sample.jspHTTP/1.1
    Accept:image/gif.image/jpeg,/
    Accept-Language:zh-cn
    Connection:Keep-Alive
    Host:localhost
    User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
    Accept-Encoding:gzip,deflate

    username=jinqiao&password=1234
  5. 服務器處理請求: 涉及服務器重定向, 301, 302狀態碼;
    反向代理客戶端不是直接經過HTTP協議訪問某網站應用服務器,而是先請求到Nginx,Nginx再請求應用服務器,而後將結果返回給客戶端,這裏Nginx的做用是反向代理服務器。同時也帶來了一個好處,其中一臺服務器萬一掛了,只要還有其餘服務器正常運行,就不會影響用戶使用。
    一、什麼是反向代理?
    客戶端原本能夠直接經過HTTP協議訪問某網站應用服務器,網站管理員能夠在中間加上一個Nginx,客戶端請求Nginx,Nginx請求應用服務器,而後將結果返回給客戶端,此時Nginx就是反向代理服務器。
  6. 服務器響應請求: 狀態行, 響應頭, 空行, 響應正文
  7. 瀏覽器解析渲染頁面: html解析
  8. 鏈接結束: 斷開涉及四次揮手

傳送門
傳送門
傳送門

301和302狀態碼

301和302狀態碼都表示重定向,就是說瀏覽器在拿到服務器返回的這個狀態碼後會自動跳轉到一個新的URL地址,這個地址能夠從響應的Location首部中獲取(用戶看到的效果就是他輸入的地址A瞬間變成了另外一個地址B)——這是它們的共同點。他們的不一樣在於。301表示舊地址A的資源已經被永久地移除了(這個資源不可訪問了),搜索引擎在抓取新內容的同時也將舊的網址交換爲重定向以後的網址;302表示舊地址A的資源還在(仍然能夠訪問),這個重定向只是臨時地從舊地址A跳轉到地址B,搜索引擎會抓取新的內容而保存舊的網址。

多進程多線程瀏覽器(好比 Chrome), 主控進程, 插件進程, GPU進程, 每一個 tab 一個進程, tab 進程內有網絡請求線程等

簡版
詳細版

協議棧各層經常使用協議

應用層:
HTTP:超文本傳輸協議, 基於TCP, 使用80號端口, 是用於從www服務器傳輸超文本到本地瀏覽器的傳輸協議.
SMTP:簡單郵件傳輸協議, 基於TCP, 使用25號端口, 是一組用於由源地址到目的地址傳送郵件的規則, 用來控制信件的發送、中轉.
FTP:文件傳輸協議, 基於TCP, 通常上傳下載用FTP服務, 數據端口是20號, 控制端口是21號.
TELNET:遠程登陸協議, 基於TCP, 使用23號端口, 是Internet遠程登錄服務的標準協議和主要方式. 爲用戶提供了在本地計算機上完成遠程主機工做的能力. 在終端使用者的電腦上使用telnet程序鏈接到服務器. 使用明碼傳送, 保密性差、簡單方便.
DNS:域名解析, 基於UDP, 使用53號端口, 提供域名到IP地址之間的轉換. 同時使用tcp 53端口
SSH:安全外殼協議, 基於TCP, 使用22號端口, 爲創建在應用層和傳輸層基礎上的安全協議. SSH 是目前較可靠, 專爲遠程登陸會話和其餘網絡服務提供安全性的協議.

GET和POST區別

GET和POST是HTTP協議中的兩種發送請求的方法。而HTTP是是基於TCP/IP的關於數據如何在萬維網中如何通訊的協議。HTTP的底層是TCP/IP。因此GET和POST的底層也是TCP/IP,也就是說,GET/POST都是TCP連接。GET和POST能作的事情是同樣同樣的, 因此給GET加上request body,給POST帶上url參數,技術上是徹底行的通的。

業界不成文的規定是,(大多數)瀏覽器一般都會限制url長度在2K個字節,而(大多數)服務器最多處理64K大小的url。超過的部分,恕不處理。若是你用GET服務,在request body偷偷藏了數據,不一樣服務器的處理方式也是不一樣的,有些服務器會幫你卸貨,讀出數據,有些服務器直接忽略,因此,雖然GET能夠帶request body,也不能保證必定能被接收到哦。

因此GET和POST本質上就是TCP連接,並沒有差異。可是因爲HTTP的規定和瀏覽器/服務器的限制,致使他們在應用過程當中體現出一些不一樣。

GET和POST還有一個重大區別,簡單的說:GET產生一個TCP數據包;POST產生兩個TCP數據包。

  • 對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據);
  • 而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。

由於POST須要兩步,時間上消耗的要多一點,看起來GET比POST更有效。所以Yahoo團隊有推薦用GET替換POST來優化網站性能。但這是一個坑!跳入需謹慎。爲何?

  1. GET與POST都有本身的語義,不能隨便混用。
  2. 據研究,在網絡環境好的狀況下,發一次包的時間和發兩次包的時間差異基本能夠無視。而在網絡環境差的狀況下,兩次包的TCP在驗證數據包完整性上,有很是大的優勢。
  3. 並非全部瀏覽器都會在POST中發送兩次包,Firefox就只發送一次。

傳送門

Cookie和Session的做用及它們之間的區別

傳送門
傳送門

  • Cookie
    Cookie是存儲在用戶本地計算機上,用於保存一些用戶操做的歷史信息,當用戶再次訪問咱們的服務器的時候,瀏覽器經過HTTP協議,將他們本地的Cookie內容也發到我們服務器上,從而完成驗證. Cookie又分爲了會話Cookie與持久Cookie,要區分這兩種類型,很是的簡單,持久Cookie就是咱們設置了它的過時時間,而沒設置過時時間的,都屬於會話Cookie由於當咱們設置了Cookie的過時時間,那麼這個Cookie就會存儲在用戶的硬盤中,而不是在內存中,由於不在內存中,無論用戶是關閉瀏覽器,仍是關機重啓,只要在有效時間內,這個Cookie都能用

  • Session
    存儲在咱們的服務器上,就是在咱們的服務器上保存用戶的操做信息用戶訪問咱們的網站時,咱們的服務器會成一個Session ID,而後把Session ID存儲起來,再把這個Session ID發給咱們的用戶,用戶再次訪問咱們的服務器的時候,拿着這個Session ID就能驗證了,當這個ID能與咱們服務器上存儲的ID對應起來時,咱們就能夠認爲是本身人

Cookie與Session的不一樣

  1. 存放位置不一樣. Cookie保存在客戶端,Session保存在服務端。
  2. 存取方式的不一樣. Cookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二進制數據,需求先進行編碼。Session中可以存取任何類型的數據
  3. 安全性(隱私策略)的不一樣. Cookie存儲在瀏覽器中,對客戶端是可見的,客戶端的一些程序可能會窺探、複製以致修正Cookie中的內容. 而Session存儲在服務器上,對客戶端是透明的,不存在敏感信息泄露的風險。
  4. 有效期上的不一樣. 只須要設置Cookie的過時時間屬性爲一個很大很大的數字,Cookie就能夠在瀏覽器保存很長時間。 因爲Session依賴於名爲JSESSIONID的Cookie,而Cookie JSESSIONID的過時時間默許爲–1,只需關閉了瀏覽器(一次會話結束),該Session就會失效。
  5. 對服務器形成的壓力不一樣 Session是保管在服務器端的,每一個用戶都會產生一個Session。假如併發訪問的用戶十分多,會產生十分多的Session,耗費大量的內存。而Cookie保管在客戶端,不佔用服務器資源。假如併發閱讀的用戶十分多,Cookie是很好的選擇。

二者結合使用

  • 存儲在服務端:經過cookie存儲一個session_id, 而後具體的數據則是保存在session中. 若是用戶已經登陸, 則服務器會在 cookie中保存一個 session_id, 下次再次請求的時候, 會把該 session_id攜帶上來, 服務器根據 session_id在 session庫中獲取用戶的 session數據. 就能知道該用戶究竟是誰, 以及以前保存的一些狀態信息. 這種專業術語叫作 server side session.
  • 將session數據加密, 而後存儲在cookie中. 這種專業術語叫作 client side session

會話機制

  • Cookie
      Cookie是客戶端保持狀態的方法。
      Cookie簡單的理解就是存儲由服務器發至客戶端並由客戶端保存的一段字符串。爲了保持會話,服務器能夠在響應客戶端請求時將Cookie字符串放在Set-Cookie下,客戶機收到Cookie以後保存這段字符串,以後再請求時候帶上Cookie就能夠被識別。
      除了上面提到的這些,Cookie在客戶端的保存形式能夠有兩種,一種是會話Cookie一種是持久Cookie,會話Cookie就是將服務器返回的Cookie字符串保持在內存中,關閉瀏覽器以後自動銷燬,持久Cookie則是存儲在客戶端磁盤上,其有效時間在服務器響應頭中被指定,在有效期內,客戶端再次請求服務器時均可以直接從本地取出。須要說明的是,存儲在磁盤中的Cookie是能夠被多個瀏覽器代理所共享的。

  • Session
      Session是服務器保持狀態的方法。
      首先須要明確的是,Session保存在服務器上,能夠保存在數據庫、文件或內存中,每一個用戶有獨立的Session用戶在客戶端上記錄用戶的操做。咱們能夠理解爲每一個用戶有一個獨一無二的Session ID做爲Session文件的Hash鍵,經過這個值能夠鎖定具體的Session結構的數據,這個Session結構中存儲了用戶操做行爲。

當服務器須要識別客戶端時就須要結合Cookie了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用Cookie來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,須要在Cookie裏面記錄一個Session ID,之後每次請求把這個會話ID發送到服務器,我就知道你是誰了。若是客戶端的瀏覽器禁用了Cookie,會使用一種叫作URL重寫的技術來進行會話跟蹤,即每次HTTP交互,URL後面都會被附加上一個諸如sid=xxxxx這樣的參數,服務端據此來識別用戶,這樣就能夠幫用戶完成諸如用戶名等信息自動填入的操做了。

介紹經常使用的請求方法

GET     請求指定的頁面信息,並返回實體主體。
HEAD    相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
POST    向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。
PUT     從客戶端向服務器傳送的數據取代指定的文檔的內容。
DELETE  請求服務器刪除指定的頁面。
OPTIONS 容許客戶端查看服務器的性能。
COPY    請求服務器將指定的頁面拷貝至另外一個網絡地址。

介紹常見的 HTTP 狀態碼(至少五個)

分類 分類描述
1** 信息,服務器收到請求,須要請求者繼續執行操做
2** 成功,操做被成功接收並處理
3** 重定向,須要進一步的操做以完成請求
4** 客戶端錯誤,請求包含語法錯誤或沒法完成請求
5** 服務器錯誤,服務器在處理請求的過程當中發生了錯誤

傳送門

介紹常見的 HTTP 頭部(至少五個)

傳送門

HTTP 中與緩存相關的頭部有哪些, 它們有什麼區別

Expires(響應)/Cache-Control(請求+響應): 前者指定過時絕對時間, 後者指定過時相對時間

Last-Modified(響應)/If-Modified-Since(請求): 修改時間

ETag(響應)/If-None-Match(請求): 把時間換成編碼

傳送門

各類刷新

假設對一個資源:
瀏覽器第一次訪問,獲取資源內容和cache-control: max-age:600,Last_Modify: Wed, 10 Aug 2013 15:32:18 GMT
因而瀏覽器把資源文件放到緩存中,而且決定下次使用的時候直接去緩存中取了。

瀏覽器url回車
瀏覽器發現緩存中有這個文件了,好了,就不發送任何請求了,直接去緩存中獲取展示。(最快)

下面我按下了F5刷新
F5就是告訴瀏覽器,別偷懶,好歹去服務器看看這個文件是否有過時了。因而瀏覽器就膽膽襟襟的發送一個請求帶上If-Modify-since:Wed, 10 Aug 2013 15:32:18 GMT
而後服務器發現:誒,這個文件我在這個時間後還沒修改過,不須要給你任何信息了,返回304就好了。因而瀏覽器獲取到304後就去緩存中歡歡喜喜獲取資源了。

可是呢,下面咱們按下了Ctrl+F5
這個但是要命了,告訴瀏覽器,你先把你緩存中的這個文件給我刪了,而後再去服務器請求個完整的資源文件下來。因而客戶端就完成了強行更新的操做...

分別介紹強緩存和協商緩存

瀏覽器緩存主要分爲強緩存(也稱本地緩存)和協商緩存(也稱弱緩存)。

強緩存
強緩存是利用 http 頭中的 Expires 和 Cache-Control 兩個字段來控制的,用來表示資源的緩存時間。
強緩存中,普通刷新會忽略它,但不會清除它,須要強制刷新。瀏覽器強制刷新,請求會帶上 Cache-Control:no-cache 和 Pragma:no-cache。
一般,強緩存不會向服務器發送請求,直接從緩存中讀取資源,在chrome控制檯的network選項中能夠看到該請求返回200的狀態碼。分爲 from disk cache 和 from memory cache。

  • from disk cache :通常非腳本會存在內存當中,如css,html等
  • from memory cache :資源在內存當中,通常腳本、字體、圖片會存在內存當

有緩存命中和緩存未命中狀態:

協商緩存就是由服務器來肯定緩存資源是否可用,因此客戶端與服務器端要經過某種標識來進行通訊,從而讓服務器判斷請求資源是否能夠緩存訪問。
普通刷新會啓用弱緩存,忽略強緩存。只有在地址欄或收藏夾輸入網址、經過連接引用資源等狀況下,瀏覽器纔會啓用強緩存,這也是爲何有時候咱們更新一張圖片、一個js文件,頁面內容依然是舊的,可是直接瀏覽器訪問那個圖片或文件,看到的內容倒是新的。
這個主要涉及到兩組 header 字段: Etag 和 If-None-Match、 Last-Modified和 If-Modified-Since。
向服務器發送請求,服務器會根據這個請求的request header的一些參數來判斷是否命中協商緩存。
有緩存命中和緩存未命中狀態:

傳送門

IP地址做用, 以及MAC地址做用

MAC地址是一個硬件地址, 用來定義網絡設備位置, 主要由數據鏈路層負責. 而IP協議提供一種統一的地址格式, 爲互聯網上的每個網絡和每一臺主機分配一個邏輯地址, 以此來屏蔽物理地址的差別

TCP/IP數據鏈路層交互過程

網絡層等到數據鏈路層用mac地址做爲通訊目標, 數據包到達網絡等準備往數據鏈路層發送的時候, 首先會去本身的arp緩存表(存放着ip-mac對應關係)去查找該目標ip的mac地址, 若是查到了, 就標明目標ip的mac地址封裝到鏈路層數據包的包頭. 若是緩存中沒有找到, 會發起一個廣播: who is ip XXX tell ip xxx, 全部受到廣播的機器看這個ip是否是本身的, 若是是本身的, 則以單播形式將本身的mac地址回覆給請求的機器

ICMP

ICMP(Internet Control Message Protocol)因特網控制報文協議。它是IPv4協議族中的一個子協議,用於IP主機、路由器之間傳遞控制消息。控制消息是在網絡通不通、主機是否可達、路由是否可用等網絡自己的消息。這些控制消息雖然不傳輸用戶數據,可是對於用戶數據的傳遞起着重要的做用。

ICMP協議與ARP協議不一樣, ICMP靠IP協議來完成任務, 因此ICMP報文中要封裝IP頭部. 它與傳輸層協議(如TCP和UDP)的目的不一樣,通常不用來在端系統之間傳送數據,不被用戶網絡程序直接使用,除了想Ping和Tracert這樣的診斷程序。

類型八、代碼0:回射請求
類型0、代碼0:回射應答

數據包通過路由會發生什麼, 路由轉發

IP數據包經由路由轉發的時候源ip,目的ip,源MAC,目的mac是否發生改變,如何改變?

A—–(B1-B2)—–(C1-C2)——-E

如上拓撲圖爲例,B1和B2是路由器B上的兩個接口,C1和C2是路由器C上的兩個接口,A和E是PC,由主機A向主機E發送數據包,那麼在主機A造成的數據包的目的IP就是E的IP,源IP就是主機A的IP地址,目標MAC地址就是B1的MAC地址,源MAC地址就是A的MAC地址

由A發給路由器B,B通過重封裝後,源IP和目標IP是不變的,源MAC地址變成B2的MAC地址,目標MAC地址變成C1的MAC地址,封裝完成發送給路由器C,路由器C接收到數據包後和B作的操做是同樣的,源IP和目標IP的不變的,源MAC地址變成C2的MAC地址,目標MAC地址變成主機E的MAC地址,而後發送給主機E,這樣E就收到了這個數據包,當恢復數據包的時候就是把收到的數據包的源IP地址(主機A的IP地址)和源MAC地址(接口C2的MAC地址)做爲他的目標IP和目標MAC地址

數據包經由路由轉發時源、目的IP地址及MAC地址變化狀況

ping 的流程

ping同一網段(主機A ping主機B)

  1. 主機A封裝二層報文, 檢查本身的mac地址, 若無B的mac地址, 則向外發送一個arp廣播(arp廣播中有a的mac地址, 無b的mac地址)
  2. 交換機收到報文後, 檢索本身有沒有保存B的mac地址, 如有直接返回給A; 若沒有, 則像全部端口發送arp廣播
  3. 其餘主機收到後, 若發現目的ip不是本身的ip, 則丟棄報文; 如果則當即響應, 並按一樣的報文返回給主機A
  4. 主機A獲得B的mac地址後, 把這個mac封裝到ICMP二層報文中(有mac地址), 想主機B發送,
  5. 主機B收到報文後, 以一樣格式返回一個值給A, 完成統一網段的ping

ping不一樣網段(主機A ping 主機C)(假設拓撲結構爲A-端口E1-端口E2-C,E1和E2爲路由器的兩個端口)

  • 主機A觀察目的IP與本機IP是否爲同一網段,結果爲不一樣;
  • 看本機是否設置了網關,若未,則目的不可達;如有,則執行下一步;
  • 發送一個ARP廣播包(以獲取路由器MAC地址),ARP廣播包的源IP爲主機A的ip,目的IP爲主機A網關IP,即端口E1的IP,源MAC爲主機A的MAC,目的MAC爲廣播MAC:ff-ff-ff-ff-ff-ff。
  • 路由器迴應ARP包:源IP爲主機A網關IP,目的IP爲主機A的IP;源MAC爲主機A網關MAC,即端口E1的MAC,目的MAC爲主機A的MAC。
  • 主機A發送ICMP包,源MAC爲主機A的MAC,目的MAC爲主機A的網關MAC,源IP爲主機A的IP,目的IP爲主機C的IP。
  • 路由器收到ICMP包後,拆包,查IP端口對照表(路由表),發現主機C的IP所在網段的數據由E2口發出,轉發包給端口E2。
  • 路由器(爲了獲取主機C的MAC)發送一個ARP包,源IP爲端口E2的IP,目的IP爲主機C的IP;源MAC爲端口E2的MAC,目的MAC爲廣播MAC;
  • 主機C發送ARP迴應,端口E2得到主機C的MAC;
  • 路由器發送ICMP,源IP爲主機A的IP,目的IP爲主機C的IP;源MAC爲端口E2的MAC,目的MAC爲主機C的MAC。
  • 主機C迴應ICMP,源IP爲主機C的IP,目的IP爲主機A的 IP;源MAC爲主機C的MAC,目的MAC爲 端口E2的MAC。
  • 路由器轉發ICMP,源IP爲主機C的IP,目的IP爲主機A的IP;源MAC爲端口E1的MAC,目的MAC爲主機A的MAC。
  • 主機A收到迴應,則完成一次ping。(跨網段ping過程當中ICMP數據報中源IP和目的IP始終是兩臺主機IP地址,可是MAC地址在變化)

ping域名

  • 首先本機發送域名請求數據到PC設置的DNS ip
  • PC經過子網掩碼判斷DNS ip是本網段仍是跨網段(這裏只考慮跨網段)
  • 因爲是跨網段,PC發送DNS域名解析數據包到PC設置的網關ip上。(此時先要進行二層的mac轉發,PC查看本機arp緩存表,若是表中有網關的mac地址,直接轉發,若是沒有,使用arp解析協議解析到網關的mac地址。以後封裝成數據幀發送到三層網絡層)此時PC發送三層數據到網關,源地址爲PC內網地址,目的地址爲DNS ip地址。而在二層源mac地址爲PC mac地址,目的mac地址爲網關mac地址。
  • 路由內網網關收到數據包,根據數據包的目的地址,查看路由表。根據路由表發送數據到下一跳上。(發送前,數據到達路由外網端口,會根據nat地址轉換配置。造成一條內網ip+port與外網ip+port的一一對應關係。)
  • 發送到下一跳和內網通訊都是同樣的,查看路由arp緩存表,若是有下一跳mac地址,就直接發送,沒有的話須要arp協議解析一下。
  • 對端路由收到數據包,再接着根據路由表判斷下一跳。這樣一跳一跳地,最後到達DNS服務器。服務器將查詢結果返回。
  • 返回的數據包在ISP的網絡裏最後尋址到你的路由器上,你的路由器收到數據包後,會查詢路由nat鏈接表,尋找ip+port關係對應的內網ip。拆分數據包,封裝成幀,最後PC收到域名對應的ip地址。
    到這裏,域名解析過程完成,接下來ping對方ip,過程與上面幾乎同樣
  • 再發起一次PC到目的域名ip地址的一次ping請求信息
  • PC經過子網掩碼判斷對方ip是本網段仍是跨網段(這裏只考慮跨網段)
  • 因爲是跨網段,PC發送數據包到網關ip上。
  • 路由內網網關收到數據包,根據數據包的目的地址,查看路由表。根據路由表發送數據到下一跳上。(發送前,數據到達路由外網端口,會根據nat地址轉換配置。造成一條內網IP+port與外網ip+port的一一對應關係。)
  • 發送到下一跳和內網通訊都是同樣的,查看路由arp緩存表,若是有下一跳mac地址,就直接發送,沒有的話就是要arp協議解析一下。
  • 服務器收到數據包後,會從新構建一個ICMP應答包,而後返回。
  • 返回的數據包在ISP的網絡裏最後尋址到你的路由器上,你的路由器收到數據包後,會查詢路由nat鏈接表,尋找ip+port關係對應的內網ip。拆分數據包,封裝成幀,最後PC收到ICMP應答數據包。

整個過程到此結束。在整個這個過程當中,源ip地址和目的ip地址是不變的(內網到路由器段不算在內)而mac地址是變的。

多路分解與多路複用

套接字尋址系統使得TCP和UDP可以執行傳輸層另外一個重要任務:多路複用多路分解。多路複用是指把多個來源的數據導向一個輸出,而多路分解是把從一個來源接收的數據發送到多個輸出。

多路傳輸/多路分解讓TCP/IP協議棧較低層的協議沒必要關心哪一個程序在傳輸數據。與應用程序相關的操做都由傳輸層完成了,數據經過一個與應用程序無關的管道在傳輸層與網際層之間傳遞。

多路複用和多路分解的關鍵就在於套接字地址。套接字地址包含了IP地址與端口號,爲特定計算機上的特定應用程序提供了一個惟一的標識。例如,FTP服務器:全部客戶端計算機使用熟知的TCP端口21鏈接到FTP服務器,但針對每臺我的計算機的目的套接字是不一樣的。相似地,運行於這臺FTP服務器上所有網絡應用程序都使用服務器的IP地址,但只有FTP服務程序使用由IP地址和TCP端口號21組成的套接字地址。

DNS

域名解析是把域名指向網站空間IP,讓人們經過註冊的域名能夠方便地訪問到網站的一種服務。IP地址是網絡上標識站點的數字地址,爲了方便記憶,採用域名來代替IP地址標識站點地址。域名解析就是域名到IP地址的轉換過程。域名的解析工做由DNS服務器完成。

解析順序:

  1. 瀏覽器緩存
    當用戶經過瀏覽器訪問某域名時,瀏覽器首先會在本身的緩存中查找是否有該域名對應的IP地址(若曾經訪問過該域名且沒有清空緩存便存在);
  2. 系統緩存
    當瀏覽器緩存中無域名對應IP則會自動檢查用戶計算機系統Hosts文件DNS緩存是否有該域名對應IP;
  3. hosts
  4. 路由器緩存
    當瀏覽器及系統緩存中均無域名對應IP則進入路由器緩存中檢查,以上三步均爲客服端的DNS緩存;
  5. ISP(互聯網服務提供商)DNS緩存
    當在用戶客服端查找不到域名對應IP地址,則將進入ISP DNS緩存中進行查詢。好比你用的是電信的網絡,則會進入電信的DNS緩存服務器中進行查找;
  6. 根域名服務器
    當以上均未完成,則進入根服務器進行查詢。全球僅有13臺根域名服務器,1個主根域名服務器,其他12爲輔根域名服務器。根域名收到請求後會查看區域文件記錄,若無則將其管轄範圍內頂級域名(如.com)服務器IP告訴本地DNS服務器;
  7. 頂級域名服務器
    頂級域名服務器收到請求後查看區域文件記錄,若無則將其管轄範圍內主域名服務器的IP地址告訴本地DNS服務器;
  8. 主域名服務器
    主域名服務器接受到請求後查詢本身的緩存,若是沒有則進入下一級域名服務器進行查找,並重復該步驟直至找到正確紀錄;
  9. 保存結果至緩存
    本地域名服務器把返回的結果保存到緩存,以備下一次使用,同時將該結果反饋給客戶端,客戶端經過這個IP地址與web服務器創建連接。

域名的層級: www.example.com真正的域名是www.example.com.root,簡寫爲www.example.com.。由於,根域名.root對於全部域名都是同樣的,因此平時是省略的。
根域名的下一級,叫作"頂級域名"(top-level domain,縮寫爲TLD),好比.com、.net;再下一級叫作"次級域名"(second-level domain,縮寫爲SLD),好比www.example.com裏面的.example,這一級域名是用戶能夠註冊的;再下一級是主機名(host),好比www.example.com裏面的www,又稱爲"三級域名",這是用戶在本身的域裏面爲服務器分配的名稱,是用戶能夠任意分配的。

遞歸查詢和迭代查詢的區別

  • 遞歸查詢
    遞歸查詢是一種DNS 服務器的查詢模式,在該模式下DNS 服務器接收到客戶機請求,必須使用一個準確的查詢結果回覆客戶機。若是DNS 服務器本地沒有存儲查詢DNS 信息,那麼該服務器會詢問其餘服務器,並將返回的查詢結果提交給客戶機。
  • 迭代查詢
    DNS 服務器另一種查詢方式爲迭代查詢,DNS 服務器會向客戶機提供其餘可以解析查詢請求的DNS 服務器地址,當客戶機發送查詢請求時,DNS 服務器並不直接回複查詢結果,而是告訴客戶機另外一臺DNS 服務器地址,客戶機再向這臺DNS 服務器提交請求,依次循環直到返回查詢的結果
    爲止。

NAT

網絡地址轉換(Network Address Translation,縮寫:NAT)在計算機網絡中是一種在IP數據包經過路由器或防火牆時重寫來源IP地址或目的IP地址的技術。這種技術被廣泛使用在有多臺主機但只經過一個公有IP地址訪問因特網的私有網絡中。它是一個方便且獲得了普遍應用的技術。固然,NAT也讓主機之間的通訊變得複雜,致使了通訊效率的下降。

DHCP

動態主機設置協議(Dynamic Host Configuration Protocol,縮寫:DHCP)是一個用於局域網的網絡協議,位於OSI模型的應用層,使用UDP協議工做,主要有兩個用途:

  1. 用於內部網或網絡服務供應商自動分配IP地址給用戶
  2. 用於內部網管理員做爲對全部計算機做中央管理的手段

EGP&IGP

1、IGP:內部網關協議,即在一個自治系統內部使用的路由選擇協議,如RIP和OSPF
一、RIP是一種分佈式的基於距離向量的路由選擇協議,要求網絡中的每個路由器都要維護從它本身到其餘每個目的
網絡的距離向量。距離便是跳數,路由器與直接相連的網絡條數爲1,之後每通過一個路由器跳數加1。RIP容許一條路
徑最多包含15個路由,所以當距離爲16時認爲不可達,這由於如此限制了網絡的規模,說明RIP只能工做到規模較小的
網絡中。RIP的三個要點:僅和相鄰路由器交換信息;交換的信息是當前路由器知道的所有信息,即路由表;按固定的
時間間隔交換路由信息,如30秒,RIP協議使用運輸層的用戶數據報UDP進行傳送, 所以RIP協議的位置位於應用層,
可是轉發IP數據報的過程是在網絡層完成的。RIP是好消息傳播的快,壞消息傳播的慢。

二、OSPF:最短路徑優先,三個要點:採用洪泛法向本自治系統的路由器發送信息;發送的信息就是與本路由器相鄰的
全部路由器的鏈路狀態,可是隻是路由器所知道的部分信息;只有當鏈路狀態發生變化時,路由器才用洪泛發向全部路
由器發送此信息。OSPF直接使用IP數據包傳送,所以OSPF位於網絡層

2、EGP:外部網關協議,若源站和目的站處於不一樣的自治系統中,當數據報傳到一個自治系統的邊界時,就須要使用
一種協議將路由信息傳遞到另外一個自治系統中,如EGP

TCP中爲何第一個起始序列號是隨機的

  1. 防止接受網絡上粘滯的TCP包,若是都從0開始的話,極其容易接受以前斷開鏈接發送的粘滯包。雖然能夠採用每次TCP會話都使用一個UUID做爲標記,可是考慮到每次都要攜帶UUID,比較浪費流量,因此就採用隨機序列號的方法。
  2. 防止Hack猜想序列號,而後假裝TCP報文,固然這種防護其實很弱。

反向代理&正向代理&透明代理

圖解正向代理、反向代理、透明代理

TCP三次握手須要知道的細節點

一、三次握手,若是前兩次有某一次失敗,會從新從第一次開始,重來三次。
二、三次握手,若是最後一次失敗,服務器並不會重傳ack報文,而是直接發送RTS報文段,進入CLOSED狀態。這樣作的目的是爲了防止SYN洪泛攻擊。
三、發起鏈接時若是發生TCP SYN丟包,那麼系統默認的重試間隔是3s,這期間不會返回錯誤碼。
四、如何模擬tcp揮手失敗?答案是iptables命令能夠過濾數據包,丟棄全部的鏈接請求,導致客戶端沒法獲得任何ack報文。

TCP與UDP的區別與適用場景

答:TCP協議棧自己是可靠,不會丟包,不會亂序,失敗會重發。UDP須要應用層作協議來保證可靠性。視頻能夠用UDP。必須使用udp的場景:廣播。

select函數能夠檢測網絡異常嗎?

答:不能夠。當網絡異常時,select函數能夠檢測到可讀事件,這時候用read函數讀取數據,會返回0.

send/recv(read/write)返回值大於0、等於0、小於0的區別

答:
recv:
阻塞與非阻塞recv返回值沒有區分,都是 <0:出錯,=0:鏈接關閉,>0接收到數據大小,
特別:非阻塞模式下返回 值 <0時而且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的狀況 下認爲鏈接是正常的,繼續接收。
只是阻塞模式下recv會阻塞着接收數據,非阻塞模式下若是沒有數據會返回,不會阻塞着讀,所以須要 循環讀取。
write:
阻塞與非阻塞write返回值沒有區分,都是 <0:出錯,=0:鏈接關閉,>0發送數據大小,
特別:非阻塞模式下返回值 <0時而且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的狀況下認爲鏈接是正常的, 繼續發送。
只是阻塞模式下write會阻塞着發送數據,非阻塞模式下若是暫時沒法發送數據會返回,不會阻塞着 write,所以須要循環發送。
read:
阻塞與非阻塞read返回值沒有區分,都是 <0:出錯,=0:鏈接關閉,>0接收到數據大小,
特別:非阻塞模式下返回 值 <0時而且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的狀況 下認爲鏈接是正常的,繼續接收。
只是阻塞模式下read會阻塞着接收數據,非阻塞模式下若是沒有數據會返回,不會阻塞着讀,所以須要 循環讀取。
send:
阻塞與非阻塞send返回值沒有區分,都是 <0:出錯,=0:鏈接關閉,>0發送數據大小,
特別:非阻塞模式下返回值 <0時而且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的狀況下認爲鏈接是正常的, 繼續發送。
只是阻塞模式下send會阻塞着發送數據,非阻塞模式下若是暫時沒法發送數據會返回,不會阻塞着 send,所以須要循環發送

socket選項TCP_NODELAY
答:通常來講,應用層send函數不會馬上把數據發出去,而是先給到網卡緩衝區。網卡緩衝區須要等待數據積累到必定量以後纔會發送數據,這樣會致使必定的延遲。
默認狀況下,發送數據採用Nagle算法。這樣雖然提升了網絡吞吐量,可是實時性卻下降了,在一些交互性很強的應用程序來講是不容許的,使用TCP_NODELAY選項能夠禁止Nagle算法,避免連續發包出現延遲,這對低延遲網絡服務很重要。 此時,應用程序向內核遞交的每一個數據包都會當即發送出去。須要注意的是,雖然禁止了Nagle 算法,但網絡的傳輸仍然受到TCP確認延遲機制的影響。

如何檢測對端已經關閉socket

答:根據ERRNO和recv結果進行判斷
在UNIX/LINUX下,非阻塞模式SOCKET能夠採用recv+MSG_PEEK的方式進行判斷,其中MSG_PEEK保證了僅僅進行狀態判斷,而不影響數據接收
對於主動關閉的SOCKET, recv返回-1,並且errno被置爲9(#define EBADF   9 /* Bad file number /)或104 (#define ECONNRESET 104 / Connection reset by peer /)
對於被動關閉的SOCKET,recv返回0,並且errno被置爲11(#define EWOULDBLOCK EAGAIN /
Operation would block /)
 對正常的SOCKET, 若是有接收數據,則返回>0, 不然返回-1,並且errno被置爲11(#define EWOULDBLOCK EAGAIN /
Operation would block */)
所以對於簡單的狀態判斷(不過多考慮異常狀況):
    recv返回>0,   正常

socket選項SO_SNDTIMEO和SO_RCVTIMEO

答:阻塞模式時須要設置超時時間,不然會卡死。

shutdown與優雅關閉

答:socket 多進程中的shutdown, close使用
當全部的數據操做結束之後,你能夠調用close()函數來釋放該socket,從而中止在該socket上的任何數據操做:close(sockfd);
你也能夠調用shutdown()函數來關閉該socket。該函數容許你只中止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你能夠關閉某socket的寫操做而容許繼續在該socket上接受數據,直至讀入全部數據。
int shutdown(int sockfd,int how);
Sockfd是須要關閉的socket的描述符。參數 how容許爲shutdown操做選擇如下幾種方式:
    SHUT_RD:關閉鏈接的讀端。也就是該套接字再也不接受數據,任何當前在套接字接受緩衝區的數據將被丟棄。進程將不能對該套接字發出任何讀操做。對TCP套接字該調用以後接受到的任何數據將被確認而後無聲的丟棄掉。
    SHUT_WR:關閉鏈接的寫端,進程不能在對此套接字發出寫操做。
    SHUT_RDWR:至關於調用shutdown兩次:首先是以SHUT_RD,而後以SHUT_WR。

服務器若是要主動關閉鏈接,能夠這麼執行:先關本地「寫」端,等對方關閉後,再關本地「讀」端。

服務器若是要被動關閉鏈接,能夠這麼執行:當read函數返回值是0時,先關本地「寫」端,等對方關閉後,再關本地「讀」端。

connect非阻塞

設置非阻塞, connect返回值判斷是否鏈接創建, 失敗加入select/poll/epoll, 關注可寫事件,

A) 當鏈接創建成功時,套接口描述符變成 可寫(鏈接創建時,寫緩衝區空閒,因此可寫)

B) 當鏈接創建出錯時,套接口描述符變成 既可讀又可寫(因爲有未決的錯誤,從而可讀又可寫)

進一步判斷
方法一:
  A)若是鏈接創建是成功的,則經過getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 獲取的error 值將是0
  B)若是創建鏈接時遇到錯誤,則errno 的值是鏈接錯誤所對應的errno值,好比ECONNREFUSED,ETIMEDOUT 等
方法二:
  再次調用connect,相應返回失敗,若是錯誤errno是EISCONN,表示socket鏈接已經創建,不然認爲鏈接失敗。

傳送門

相關文章
相關標籤/搜索