从tcp到netty(一)
發現自己近一年有些毛病,自己也算是研習了不少的源代碼,看了不少的技術書籍,但是自己就是記憶力不行,總是過段時間就會忘記,忘記之后還得從頭開始啃源碼、啃書籍。而且有些重要技術點也會遺忘,導致再學習的時候發現自己又回到了起點!我總結為,就是自己近一年期間犯懶,沒有再寫一下博客,技能點不能很好的再回顧!
趁著發現自己的問題,同時自己也在做前后端rpc分離實踐,現在將之前研習netty的結果再總結出來,寫到博客上!
首先,我們要確定java中的netty用來做什么的?具體的工作模式優勢不解釋,網上能找一大堆,主要講它通信這塊的rpc,高效穩定的協議棧繞不開tcp/ip協議!
本來是不想記錄tcp/ip的,這個實在是沒有太多好說的,但是也發現雖然自己明白,有時候卻也是會遺漏要點知識,所以也還是記錄一下吧!
似乎作為上層程序員只需要了解tcp的握手與揮手情況即可!
首先要了解tcp/ip的頭部結構
16位源端端口 ??| ?16位目標端端口
32位序列號(發送端確認信息)
32位確認序號(服務端確認)
4位偏移量(每個數字表示1個4字節,所以最大表示15個4字節,也就是60字節)----
6位保留位(記不太清了) ?| 標志位:包括6種報文段 urg、ack、rst、syn、fin、psh
16位校驗和 ????| ????16位緊急指針
16位窗口大小(用于tcp緩沖區的控制)
40位填充字段。
我覺得挺容易理解的,作為通信雙方必須知道相互的地址,所以要有源地址與目標地址的標記;序列號與確認序列號,這個涉及到數據包分片,比如mtu導致了數據分片,如何分片與如何重組分片;偏移數據表示tcp頭部結構能夠占用的最大數據長度;保留位大概就是保留用的吧;標記位,6個就是我們常說的同步報文段、確認報文段的標記;窗口大小表示一次傳輸數據的大小,由于是16位,按照二進制計算也就是65535字節;校驗和據說是為了教研數據有效性的,我通過抓tcp的包看到類似這樣的;16位緊急指針,這個是配和urg報文段設置優先緊急數據的。
然后是三次握手,與四次揮手中tcp狀態的轉移。都是基于上面tcp頭部結構進行變化的。
三次握手
1):client將syn標志位置1,序列號seq置x(隨機數), 發送到server端,client狀態為同步發送狀態,表示等待server端的確認;
2):server端收到client端數據包,判斷syn=1,了解到為同步報文段。則這時,將要發送數據的tcp頭部分別設置syn與ack為1,確認序列號x+1,序列號置y(隨機數),發送到
client端,server端狀態為同步接收狀態;
3):client端收到serve數據包之后,判斷是否ack報文段為1,確認序列號是否為x+1,符合則將要發送的數據的tcp頭部的ack報文段置1,確認序列號y+1,然后發送;到達server端
之后,檢查ack報文段是否為1 && 確認序列號是否為y+1,符合則建立連接。
此時client與server都進入了建立成功階段。
四次揮手
1): client將要發送報文信息的tcp頭部的fin報文段置1,序列號置隨機值x,發送給server端,client端狀態為結束等待狀態;
2): server端收到fin結束報文段,將發送報文信息的tcp頭部ack置1,確認序號為x+1,server端進入關閉等待階段;
3):server端再次發送一個報文數據,tcp頭部fin置1,此時server端進入了最后確認階段;
4):client接收到了server的ack報文信息與fin報文信息之后,驗證確認序列號x+1,然后再向server端發送一個tcp頭部ack=1的報文段,client進入time_wait階段,server端收到信息
驗證后進入關閉狀態;
最后的時刻client等待2*msl(最大報文傳輸時間)時間長度,進入close狀態。
第二繞不開的一點就是網絡的io模型!一般我們應該都知道有幾種常談的io模型,阻塞式、非阻塞、異步io、多路復用、信號驅動的io模型,這里java常用的就是非阻塞式io與多路復用io也就是nio!nio基reactor的模型進行設計的。所以接下來要講一下nio與reactor模型。
Nio,這個我們需要去看epoll的解釋,最好去看epoll的源碼,這里我建議看一下深入理解nginx這本書中的epoll的解釋,詳細描述了代碼狀況!下面具體談一下epoll的優秀思想設計。
首先要知道原來模式的弊端,原有linux2.6之前的操作系統,采用select/poll的形式這兩種形式在進行連接事件的收集的時候,是將所有活躍與不活躍的套接字由用戶態的內存向操作系統的內核內存傳遞,然后由操作系統內核掃描所有套接字,不管是內存還是cpu都造成巨大的浪費。而epoll則不是,它定義了一個文件系統,首先,調用epoll_create創建一個epoll對象;然后,調用epoll_ctl向epoll添加所有的連接套接字;第三,調用epoll_wait收集活躍的連接。這樣它就避免了完全的內存復制,只進行收集活躍連接,然后也不用遍歷原來所有的連接,使cpu也降溫。
Eventpoll是創建epoll時候的數據結構,里面是一種紅黑樹的數據結構,用于存儲epoll_ctl向epoll對象中添加的事件,為什么使用紅黑樹,因為如果出現重復的套接字可以很快的識別出來。據書上說,被添加到epoll中的時間都會與網卡建立回調關系epoll_callback。Eventpoll中含有一個紅黑樹的根節點rbr與雙向鏈表rdllist,回調事件會放到rdllist,所以epoll_wait掃描事件連接的時候就只針對這個rdllist的鏈表進行遍歷,然后將事件復制給用戶內存,所以也就保證了效率。
第三,Reactor模式(也有人叫做反應器模式)!Netty中的nio線程模型命名了三種模式:Reactor單線程模型、Reactor多線程模型和主從多線程模型!實際上都是根據上邊epoll的三階段進行的劃分,所以抽象出這三種階段才能更好的理解這三種設計的模型,也就是得理解epoll的三種事件!所有這三種模型都是圍繞著三種階段進行的。
單線程模型:就是說把這三個階段統統放在一個線程上操作。該線程負責,創建事件對象,accept收集活躍socket,分發器分發二進制消息進行讀寫操作(接收數據、發送數據)。
多線程模型:這個其實主要是將收集活躍的socket與事件分發分成了兩步;一個專門的線程用于收集活躍的socket,另一個線程池專門負責數據的讀寫操作;
主從多線程模型:這個其實是在多線程模型上進一步的加固;用來收集活躍socket的線程改成了線程池,acceptor接收到tcp socket接收驗證(這個應該是指的tcp的數據驗證)連接后,將該連接的通道交給io處理線程池的一個線程上進行io的讀寫操作。
實際上就是這么回事,只是將原來串行的操作改成了并行化,同時保證線程的安全性!
轉載于:https://www.cnblogs.com/kevinfuture/p/8494648.html
總結
以上是生活随笔為你收集整理的从tcp到netty(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java爬虫问题二: 使用jsoup爬取
- 下一篇: 【转】如何掌握所有的程序语言