數據傳輸穩定性優化
以前全部的操做都是基於字符串,咱們發送字符串的時候帶有結束符,接受的時候也是讀取結束符做爲他的分割,以前的操做並無嚴格的去校驗 每個字節而且獲得他的結束符,而後進行分割,而是直接讀取到結束符爲止。在這樣的狀況下,咱們會出現一系列其餘意外的一些問題。雖然 說讀取效率更高了,由於咋們一次性把全部的東西都讀取出來了,可是他又帶來了數據穩定性上的問題。什麼問題呢?假如說客戶端發送一條數據 過來,那麼服務器端收到這條數據作出真確的行爲。可是若是客戶端發送了兩條數據,那有可能在服務器端把他當作一條數據進行接受了。 若是咱們按照以前的規則,讀取每個字節而且判斷每個字符的一個結束符是否爲換行符的話,那麼這樣的消耗是很是高的,由於這須要一個一個 字符進行校驗,當咱們發送一個大文件的時候或者大批量數據的時候,這消耗是服務器沒法承擔的。安全
消息粘包
- TCP本質上並不會發生數據層面的粘包。TCP底層是分包機制,一個包一個包的發送,這樣的狀況下數據並不會發生粘包。咱們說的粘包不是TCP層 面的粘包,而是業務層面的粘包。
- TCP的發送方與接收方必定會確保數據是以一種有序的方式到達客戶端。
- TCP是會確保數據包的完整性。
- UDP是不保證消息完整性的,因此UDP每每發生丟包等狀況。
- TCP數據傳輸具備:順序性、完整性。
- 在常規所說的Socket「粘包」,並不是數據傳輸層面的粘包。
- 「粘包」是數據處理的邏輯層面上發生的粘包。
- 這裏所說的「粘包」:包含TCP、UDP甚至其餘任意的數據流交互方案。
- Mina、Netty等框架從根原本說也是爲了解決粘包而設計的高併發庫,還有調度上的優化。
消息粘包圖
咱們在邏輯層面發送了M1 M2 M3 3條數據,理想的數據接受狀況是M1 M2 M3。但實際接受狀況有可能會是M1M2同時到達,而且別同時接受,以後 才接受M3,這是M1M2就是所謂的消息粘包。
消息不完整
- 從數據的傳輸層面來說TCP也不會發生數據丟失不全等狀況。從傳輸層面來說必定能夠確保數據發送過去並被接受,這是傳輸層面的保證。TCP一旦 出現傳輸層面的丟包或是粘包,那這個鏈接必定出現了異常,必定沒法再進行後面的數據傳輸,這個時候Socket必定是處於斷開的狀態。
- 一旦出現數據丟失不求等狀況必定是TCP中止運行終止之時。
- 「數據不完整」依然針對的是數據的邏輯接受層面。
- 在物理傳輸層面來說數據必定是能安全的完整的送達另外一端。
- 但另外一端可能緩衝區不夠或者數據處理上不夠完整致使數據只能讀取一部分數據。
- 這種狀況稱爲「數據不完整」「數據丟包」等。意思是當你發送一串比較長的消息的時候,那麼這個消息在你發送的過程中可能會被TCP分紅 一個一個小包,小包發送到服務器的時候,服務器收到這個小包發現前面的一部分已經足夠組裝一個大包的時候,他會完成一個組裝,並把 這個包push到業務上層,這個時候業務上層就說有消息來了,能夠經過channel讀取數據到Buffer當中,這個時候就開始讀取了,可是讀取 過程中,並不知道這一串大的消息究竟有多長,我僅僅只是說讀到他返回爲0(就是讀不到數據)的時候,咱們就進行後面的流程了,有可能 會出現咱們僅僅只接受到了大消息的一半的狀況,也便是把一個大的消息當成了2個子消息來處理了,這樣的狀況就是消息不完整,固然這是 接受層面上的問題。 還有一種狀況是客戶端發送一個大的數據過去,這個大數據在服務器的網卡層面已經徹底接受到了,而且在系統底層的緩衝區裏面緩衝下來了 這個時候咱們須要把它讀取到咱們的Buffer當中,可是Buffer僅僅只有100個字節,而這個大數據有200個字節的狀況下,也只能讀取前100個 字節,後面還有100個字節沒有讀,這個時候我認爲說已經滿了,他就拿去用了,這時有可能也會出現用上面的問題,這也是消息不完整的情 況。
*** 如何有序的混傳數據 咱們消息數據能夠無限的發送,不用去管它底層的傳輸,可是咱們接受的時候要可以保證,咋們消息是怎麼發送過去的,就要怎麼樣接受回來,這 才能保證消息傳輸是有意義的。服務器
- 數據傳輸加上開始結束標記。開始和結束能夠同時加,也能夠只加結束,或者說只加開始,那咱們所謂的換行符也就是結束標記,若是說咱們認爲 全部的數據都是具有一個換行符的,那咱們發送這條數據時候,當遇到換行符的時候,咱們就認爲說這條消息已經結束,再開始接受後面的 消息。一旦後面的消息也具有換行符,那麼後面的消息也表明一條獨立的消息,能夠就是所謂的能夠直接發送一行,而後讀取一行。
- 數據傳輸使用固定頭部的方案。意思是能夠在要發送消息的前面加上一個固定的特殊字符,好比說換行符或AAA或在頭部加上描述信息 描述後面 數據的具體內容的信息,確保對內容進行一個分割。
- 混合方案:固定頭部、數據加密、數據描述。
不管是假設頭部仍是尾部都會影響性能。由於我在接受消息的時候根本不知道消息是否結束了,意味着我要對每個字節進行校驗。這個校驗有 多是我從網卡上面讀取一個字節我就進行校驗。也能夠說我從網卡上面一次性把消息都讀取到Buffer當中,而後我再到buffer當中去進行 一個校驗。併發
提倡的是固定頭部的方案。框架
起止符方案
固定頭部描述方案
在服務器端我會首先讀取前面4個字節,咱們能夠將它轉換成int類型,int值能夠存儲後面消息體具體的長度。加入int 存儲的是100個字節,那麼這個時候,我讀取的時候直接從channel當中直接讀取100個字節到咋們的一個buffer當中,那麼我就認爲這就是一段 完整的消息了,我就把這100個字節直接轉換成String,而後作後續的處理。相對起止符方案他更加優秀,優秀在傳輸上面更加高效。消息不完整 和粘包都會被避免。
起止標記技術實現
固定頭部技術實現
借鑑學習HTTP精髓
- HTTP如何識別一個請求。在HTTP1及之前,每一次請求他都是一個單獨的Socket鏈接,而後發送數據 傳輸數據 返回數據,而後再端口Socket。 從HTTP2開始,咱們能夠去實現它的複用邏輯,也就是能夠創建一個Socket,進行不少次的發送和返回。
- HTTP如何讀取請求頭,請求頭協議是怎樣的。
- HTTP如何接受數據包體。
- 當數據爲文件時,HTTP如何判斷文件已接受到底了。
HTTP 1.X
重點在於描述,描述每個區間的數量,拿到Global Header能夠獲得一個總的長度,拿到總的長度以後,解析出Packet Header,拿到Packet Header以後能夠獲得Timeval、Capture length、Packet length,拿到這些長度信息以後,咱們就能夠讀取Packet data。這個地方也就是所謂 的在header裏面封裝了body部分具體有多長
每一個請求頭信息使用換行符一行一行的換行,這是一個總體,都是請求頭,這個請求頭在HTTP裏面他們經過16個字節判斷請求頭究竟有 多長,那當我知道請求頭有多長的時候,我會一致性把請求頭的信息所有讀取出來,造成一個大的字符串,而後根據換行符拆分紅各自小的字符串 ,拆分紅小的以後,我就能知道你具體的請求信息了,拿到請求信息以後,同時我能夠根據前16個字節就能以後後面數據究竟有多長,這些都是具體 的約束和規範。
HTTP 2.X
他有可能會通過一系列的握手說要常常咋們的安全校驗,而後進入到咋們的程序層,這個地方HTTP1和HTTP2他們之間的區別 是HTTP2具有一個 一幀一幀分開的概念,這個後面咱們會把一個大的數據包拆分紅小的數據包進行發送,這種狀況就是咋們分包的概念來實現。 每一個分包也是很簡單的。HTTP1當中一個消息一個發送,他就是一個有頭部和有數據的大消息體。而HTTP2當中,他可能會分紅,把頭部部分分紅 HEADER部分,而後再分紅DATA部分,兩個部分單獨的進行發送,而且單獨的進行傳輸,而這樣的過程有助於咋們服務器端接受不一樣的部分或者拒絕 某一個部分。
這是長鏈接的一種方式。首先咱們請求一條消息,咱們發送一個請求頭到服務器。首先是Request Message,Request Message裏面有一個 HEADER frame,這是請求頭的信息。服務器接受到了以後說,你這個信息我能夠進行響應,這個時候他就回送了一條消息,回送的消息裏面也是 包括了頭部,而後還有一個具體的body部分,他不是單一body,他是一個複合型的body,也就是DATA frame stream1,stream1我丟過來給你, 後面還有一系列的stream2 stream3....,這就是咋們的一整套流程。這個東西也就實現了咋們的一個長鏈接,而後你往服務器端說,我如今想要 拿到當前的一個未讀消息,那麼服務器端這個時候有未讀消息,就返回給你。若是說沒有 他會等待一下,直到他有未讀消息,他會把這個推送給你。 這是能夠用來作推送的。你能夠對一個鏈接進行屢次請求,屢次請求頭,第一個請求頭想要的是一個主頁,第二個請求頭想要的是about頁面, 那麼它也會經過不一樣的數據返回給你。這一整套流程都是創建在咋們的一個有序的一個有規矩的一個消息的封包上面,也就是咋們具體要去實現的 部分。
你能夠經過HTTP2創建一個鏈接,就能夠實現說你能夠拿很是多的信息。
HTTP2.X Header 9-byte,在HTTP2上面有一個頭部,頭部上面有個一個標準的9個字節,前面3個字節也就是24個bit,這24個bit用來 表示咋們的一個長度,也就是最大長度等於2的24次方。以後一個字節用來作type的校驗。以後32個bit,前面是一個flags flags是個特殊的標誌 位,flags以後的數據用來標識咋們HTTP2的一個特殊的惟一標識。在以後是R R是個boolean值 這也是標誌位,R後面是流的基本定義,流的惟一 標識,仍是每一幀的數據承載。這就是HTTP2.X的框架。他前面有個東西就是咱們說的長度描述,type描述,flags標誌位,這3個是咱們比較看 中的地方,也是咱們須要借鑑的地方,假如我給個人消息,前面3個字節用來標識長度,咱們也使用type用來標識是否對傳輸的數據進行加密,若是 說是加密的或者沒加密的,咱們再根據狀態是否讀取他後面的一個或兩個字節用來判斷咋們具體的加密類型。咱們要學習的地方也就是HTTP2.X的 框架概念。
混傳數據總結與梳理
構建有序消息體:
- 數據包分析與特徵提取。
- 數據頭部構建。
- 數據頭、數據體接收。
類之間的關係:
- connector
- Sender & Receiver
- 新增的3個類
基於發送的流程來看
SendDispatcher(發送調度着)
Send(發送真實的人)異步
SendDispatcher(發送調度着)當中有個一個queue隊列,而後咱們把Packet Put到隊列當中,Put進去以後 就會take拿一個Packet出來,拿出來 以後,咱們會把Packet當中的數據寫入到IoArgs,當把數據寫入到IoArgs以後,咱們會把這一份IoArgs進行一個註冊,那麼註冊到咋們的Sender, 這個時候會調用咋們sender.sendAsync(args, listener)異步發送的方法,並把IoArgs傳遞進去,還會傳遞一個listener的回調,當sender 通過了selector事件機制的回調以後,會判斷說這個時候sender能夠進行發送數據了,而且這個時候會把咋們IoArgs裏面的數據真實的拿去發送。 當他把數據發送好了以後呢,他會進行一個回調,回調回來天然也就回調咋們的listener,回調listener的什麼方法呢,就是onCompleted(IoArgs args) 完成的回調,就是說當前的這個IoArgs已經發送成功了。發送成功了 回調回來,天然這個listener是由誰來持有的呢,是由咋們的發送調度着所 持有的(SendDispatcher)。若是說此時,當前的這個Packet尚未徹底的發送完成,那麼它還會把Packet當中的數據再次的寫入到IoArgs裏面去 而後進行一遍上面的流程,直到咱們Packet被真實的完成了。固然這個地方還涉及到咋們的一個包頭和包體的概念。那包頭和包體的概念也就是 首先會提取咋們Packet當中的一個數據長度和數據的類型,而後咱們會把數據長度和數據類型在第一個包的最前面寫入到IoArgs裏面去,先把長度 和類型經過Sender發送出去,而後才發送咋們Packet當中的真實的內容,也是同樣把真實的內容寫入到IoArgs當中,而後在經過Sender發送。當 包頭和包體都發送完成以後,他會幹一件事情,他會從queue隊列當中再拿下一個Packet。若是有再重複上面的流程。高併發