歡迎你們搜索「小猴子的技術筆記」關注個人公衆號,領取豐富面試資料和學習資料。java
你瞭解TCP緩衝區嗎?它和TCP傳輸中的粘包和拆包有什麼關係呢?粘包和拆包分別發生在TCP的那個階段呢?程序員
先簡單回顧下TCP概念:在網絡傳輸中TCP是面向鏈接的、可靠的、雙通道、字節流一對一傳輸。TCP雙方通訊必需要先創建鏈接,而後分配必要的內核資源。雙方交換完畢數據以後必須都要斷開鏈接用來釋放系統資源,長連接能夠沒必要斷開鏈接複用同一個通道。那麼什麼是TCP的緩衝區呢?面試
操做系統中有兩個空間:用戶空間和內核空間。每個socket鏈接都是在內核空間,內核針對每個socket都有一個發送緩衝區和接收緩衝區。TCP的雙工工做模式以及流量控制就是依賴這兩個緩衝區的填充來實現的。算法
咱們以前用socket獲取「OutputStream」獲取一個輸出流進行字節的寫出,實際上是寫入到了「send buffer」發送緩衝區中,這個時候數據不必定會發送到對方機器上。「write()」方法僅僅是將用戶空間數據拷貝到了內核發送緩衝區中,具體何時發送由TCP決定。網絡
TCP會從發送緩衝區中把數據經過網卡發送到目標機器的內核緩衝區中。若是系統一直沒有調用"recv()"方法進行讀取的話,那麼數據將會一直擠壓在socket的recv buffer中。
TCP 粘包、拆包問題的由來:socket
若是你看懂了上面這幅圖的話,那麼對於粘包和拆包的問題就比較好理解了。在這裏我想先問一個問題,粘包和拆包是發生在傳輸過程當中嗎?性能
粘包和拆包問題究竟發生在什麼階段?首先咱們須要清楚地瞭解TCP數據是可靠的,所以確定不是傳輸的過程當中!由於數據發送是從緩衝區->網卡,所以粘包問題是從緩衝區讀取數據的時候發生的。拆包則是從緩衝區到網卡的階段發生的。學習
這裏先解釋下粘包:所謂的粘包就是發送方在同一時刻發出了兩個或者兩個以上的包到接收端。spa
假設發送端須要發送兩條數據「別緊張,你這樣沒事的!」和「好好看文章,你必定能夠學會」。首先會把這兩條數據放到發送緩衝區中,而後在通過網卡進行數據的發送到接收方的接收緩衝區中。若是接收方沒有及時從接收緩衝區中獲取往外取數據,那麼數據就會在緩衝區擠壓,這樣兩條數據就會積壓在一塊,就成了一條數據,這就是粘包的問題!
那麼什麼是拆包問題呢?拆包問題是TCP每次發送的長度是有限制的,若是發送一個包的數據過大的話,TCP就會把這個包拆成兩個包來進行發送。操作系統
假設要發送的數據「別緊張,你這樣沒事的!」很大,TCP在發送的時候把它拆成了「別緊張,你這樣」和「沒事的!」進行發送,那麼在接收方就會收到兩個報文,這就是拆包的問題。
實際上過大的話,還有可能會被拆成三個或者更多的包進行發送。可是不管被拆成幾個包,TCP都可以保證發送包的順序性和正確性。
那麼產生粘包和拆包的緣由是什麼呢?這個和TCP的緩衝區與滑塊窗口、MSS/MTU限制、Nagle算法有關。
有了粘包和拆包的問題,咱們在實際的開發中應該怎麼避免或者處理這個問題呢?那就是定義咱們的通信協議。這樣若是粘包了就能夠根據協議來區分不一樣的包,若是拆包了就等待數據構成一個完整的消息以後在進行處理。
第一種方式---定長協議:所謂的定長協議就是指定一個報文的固定長度,每次雙方按照約定截取固定的長度。假設咱們須要發送「hello」和「very」兩個單詞,按照約定的5個字節進行一次截取。那麼不足5個字節的單詞能夠添加0做爲補充,則發生的規則以下。
因爲不足約定長度的須要進行補0,所以定長協議會形成帶寬的浪費。
第二種方式---特殊字符分隔符:使用特殊字符分隔符就是在報文的結尾進行追加特殊字符分隔符,用次分隔符來標註這是一個完整的報文,例如遇到了「\n」。
這樣雖然能夠對報文進行劃分,可是要求就是報文中不能包含特殊分隔符。
第三種方式---固定頭長度:發送數據以前,須要先獲取須要發送內容的二進制字節大小,而後在須要發送的內容前面添加一個固定長度頭整數,表示消息體二進制字節的長度。
這種方式避免了特殊字符帶來的問題,是生產中能夠採起的一個方式,我在以前的文章中有介紹過這樣的使用方法。
其實對於java程序員來講,咱們沒必要過度關心接收和發送緩衝區,須要瞭解其概念,由於底層已經爲咱們作了封裝。明白「粘包」和「拆包」發生的過程和緣由。
經過觀察用戶空間和內核空間的數據交互,你也許會發現進行一次完整的交互須要進行四次的數據拷貝,這在性能上可能會有所影響。這也就有了面試官常常問的「零拷貝」的問題,嘗試着本身對本文的理解學習一下「零拷貝」,這是爲後面學習Netty打下堅實的基礎。