參考:Netty工作原理
Rpc通信,無非就是通過網(wǎng)絡(luò)層的一些可靠協(xié)議進(jìn)行數(shù)據(jù)傳輸?shù)?!這里我們的協(xié)議是tcp協(xié)議,要對tcp的握手揮手知識做一個(gè)了解,也要涉及一些多路復(fù)用、事件驅(qū)動內(nèi)容!知其然,也要知其所以然。比如為什么選用這種技術(shù)就能保證我們的業(yè)務(wù)與效率呢!
Tcp/Ip參考模型:
模型看起來很抽象,換個(gè)方式看的話會更方便一些!首先還是要先了解一下tcp/ip參考模型:應(yīng)用層、傳輸層、網(wǎng)絡(luò)層、鏈路層。單純看名稱大概也能知道各層是干什么用的,但是如何用卻顯得比較雞肋!
可以借助一些工具,分析一下tcp或者h(yuǎn)ttp傳包過程中的數(shù)據(jù),因?yàn)樵诟鲗佑猛?,肯定有一些各層需要使用的?shù)據(jù)!

隨便抓了個(gè)http的二進(jìn)制包,按照分層模型可以很明顯看出都是用來做什么的。
Frame:物理層;
Ethernet II:數(shù)據(jù)鏈路層的頭部結(jié)構(gòu),14字節(jié)。包括,源mac地址與目標(biāo)mac
地址,各占6個(gè)字節(jié),還有兩個(gè)字節(jié)表示互聯(lián)網(wǎng)協(xié)議,本次分析的是ipv4的協(xié)議;
Internet Protocol Version:網(wǎng)絡(luò)協(xié)議,IP包頭部結(jié)構(gòu),頭部信息20字節(jié)!包括, 版本、差分區(qū) 域、總長度、標(biāo)志、源ip、目標(biāo)ip等;;
Transmission Control Protocol:傳輸控制層,20字節(jié)!包括了源端口、目標(biāo)端口、 序號、確認(rèn)序號、tcp 的6種報(bào)文段等;
Hypertext Transfer Protocol:超文本傳輸協(xié)議。
總之,從抓到的包中可以很輕松容易的看出,各個(gè)字段的意義,且很容易理解!
直接上圖分析傳輸控制部分:

同時(shí)注意!雖然在工具上是按照順序排列下來的,但在碼流當(dāng)中,并不一定是如此排列的,不過同一層的內(nèi)容肯定是緊密排列的。
而主體數(shù)據(jù),不管是公有協(xié)議http還是私有rpc都是一個(gè)header頭與一個(gè)body體的結(jié)合。像私有棧的rpc屬于自定義內(nèi)容信息。
三次握手、四次揮手:
要了解tcp,必須要先了解tcp的報(bào)文格式!事實(shí)上根據(jù)以上的具體報(bào)文數(shù)據(jù)段,我們已經(jīng)知道了這個(gè)報(bào)文格式!
本來想自己畫一下,但是發(fā)現(xiàn)自己畫的并不好看,而網(wǎng)上的報(bào)文格式似乎都是用的同一張圖片,感覺不錯(cuò),直接貼過來:

關(guān)于tcp連接,必須要把握好6個(gè)標(biāo)記位所表示的內(nèi)容!URG:緊急報(bào)文段;ACK:確認(rèn)報(bào)文段;PSH:推數(shù)據(jù)報(bào)文段;RST:重置報(bào)文段;SYN:同步報(bào)文段;FIN:結(jié)束報(bào)文段。所謂三次握手,四次揮手也主要是針對這幾個(gè)報(bào)文段標(biāo)記位的建立連接、斷開連接的操作!
假設(shè)握手、揮手過程中:任意一端主動方為client端!這里同時(shí)還會涉及到client與server的各種狀態(tài)!
三次握手:
1)、執(zhí)行觸發(fā)Client端,隨機(jī)設(shè)置序號seq為x,同時(shí)設(shè)置syn報(bào)文段 = 1,將此報(bào)文段發(fā)送給server端,此時(shí)client端處于SYN_SEND狀態(tài);
2)、server端接收到數(shù)據(jù),根據(jù)client.syn知道要建立連接,ack報(bào)文段、syn報(bào)文段 = 1,同時(shí)ack seq = client.seq+1,同時(shí)產(chǎn)生一個(gè)seq序號 = y,將此數(shù)據(jù)發(fā)送給client,此時(shí)server為SYN_RECV狀態(tài);
3)、client接收到數(shù)據(jù),驗(yàn)證server.ack = 1 && ack seq報(bào)文段 = y + 1,是,則置ack = 1, 發(fā)送給server端;server端檢驗(yàn)ack = 1 && ack seq = y + 1,是,則成功建立。Client、server先后進(jìn)入ESTABLISHED狀態(tài)。
四次揮手:
1)、執(zhí)行觸發(fā),client端將fin報(bào)文段 = 1、seq=x,發(fā)送給server端,此時(shí)client端處于FIN_WAIT_1的狀態(tài);
2)、server端收到數(shù)據(jù),發(fā)現(xiàn)fin = 1,則將ack=1,ack seq = x + 1 = y發(fā)送給client,此時(shí)server進(jìn)入CLOSE_WAIT階段;
3)、server端再次發(fā)送一個(gè)fin報(bào)文段,關(guān)閉與client端的數(shù)據(jù)傳輸,server狀態(tài)變?yōu)長AST_ACK;
4)、client端收到fin報(bào)文段之后,先進(jìn)入TIME_WAIT階段,再根據(jù)上述規(guī)律發(fā)送一個(gè)ack seq = y + 1 給server端,server端收到數(shù)據(jù)變?yōu)镃LOSED狀態(tài)。

具體可以通過該圖,流的分析驗(yàn)證。如果要查看tcp的各個(gè)狀態(tài),使用netstat -apn查看:

可以看出此機(jī)器的運(yùn)行正常!
至于為什么要進(jìn)行四次揮手,主要是因?yàn)楫?dāng)server端接收到fin之后,client不發(fā)送了,但是server端還可以接收并發(fā)送,所以要處理掉server接收發(fā)送的情況。
假如是email時(shí)代,兩個(gè)熱戀的情侶:1、女方發(fā)消息 ‘end’;男方收到email后,2、首先一點(diǎn)需要停止 ‘發(fā)送消息’,3、也要告訴女方“了解,即將關(guān)閉”;女方確認(rèn)之后,4告訴男方,‘好的’,這樣才就結(jié)束了!不然的話,如果男方是一個(gè)話癆,女方發(fā)送就關(guān)閉消息了,那么男方可能不知道女方是不是接收到了最后的消息,也就不能及時(shí)斷開!
那么如果真的出現(xiàn)了女方發(fā)送了消息就不再進(jìn)行回復(fù)的情況呢?也就是比如被動關(guān)閉的情況,女方無法再發(fā)送ack確認(rèn)報(bào)文了怎么辦???這個(gè)時(shí)候就有了2MSL(最大報(bào)文生存時(shí)間),這樣的定義。也就是如果超過了2倍的發(fā)送、接收時(shí)間,這可以保證女方(client端)沒有再發(fā)送過來數(shù)據(jù),不會出現(xiàn)丟包的情況!
長短連接:
常見的http一般是采用短連接的,也可以通過keepAlive設(shè)置長連接,這里因?yàn)閞pc模塊是用在內(nèi)網(wǎng)訪問的,為了避免頻繁創(chuàng)建關(guān)閉連接,可以創(chuàng)建一個(gè)tcp的長連接!
全連接半連接:
有一個(gè)參數(shù)allowHalfOpen!allowHalfOpen為false,socket 將發(fā)送一個(gè) FIN 數(shù)據(jù)包,一旦寫出,它的等待寫入隊(duì)列就銷毀它的文件描述符。當(dāng)然,如果allowHalfOpen?為?true,socket 就不會自動結(jié)束;它的寫入端,用戶可以寫入任意數(shù)量的數(shù)據(jù)!
所謂半連接,也就是在第二次握手之后,出現(xiàn)的狀態(tài)!Client發(fā)送了syn報(bào)文段給server端,client端為同步發(fā)送狀態(tài);server端收到信息,并發(fā)送了ack+syn報(bào)文段給client端,此時(shí)處于同步接收狀態(tài),此時(shí)server端即為半連接狀態(tài)。為什么要有這么一個(gè)參數(shù)呢?那是因?yàn)?,如果在同步接收狀態(tài),任意client端可以偽造ip地址請求,對server發(fā)送SYN報(bào)文段,由于ip不存在,tcp機(jī)制會不斷重試重發(fā),最終導(dǎo)致端口占用,網(wǎng)絡(luò)阻塞,也就是syn攻擊的一種。如果存在allowHalfOpen這個(gè)參數(shù)為false,就可以及時(shí)釋放資源。
全雙工:
通信方式不能是單方向的,所以必須是client、server都能進(jìn)行發(fā)送與接收數(shù)據(jù)!
noDelay:
涉及到一種傳輸方式與nagle算法。Nagle算法的基本定義是任意時(shí)刻,最多只能有一個(gè)未被確認(rèn)的小段(摘自百度百科)。簡單來說就是在mtu等造成的分片影響下,通過該算法,確保在網(wǎng)絡(luò)中只有一個(gè)報(bào)文段,也就是當(dāng)psh數(shù)據(jù)后,在未收到ack確認(rèn)報(bào)文段之前不發(fā)送數(shù)據(jù)!這種算法毫無疑問會是效率低下,但保證信息可靠性提升了網(wǎng)絡(luò)吞吐率。
backlog隊(duì)列:
這是底層對接收發(fā)送數(shù)據(jù)處理的一個(gè)隊(duì)列!事實(shí)上這也是對半連接的區(qū)分,如果看netty源碼,也會發(fā)現(xiàn)會調(diào)用java底層對應(yīng)這個(gè)參數(shù)的值!Linux2.X哪個(gè)忘了,版本之后,采用此方式!實(shí)際上是有兩個(gè)隊(duì)列,一個(gè)是syn backlog、另一個(gè)是listen backlog(accept隊(duì)列),這其實(shí)也是nio處理的方式,異步的處理!如果accept隊(duì)列滿了后,會如何?這時(shí)候,底層不處理,也就是不返回ack,client端會認(rèn)為是消息丟失,不斷進(jìn)行重試,直到處理完成!
拆包:
在提升tcp網(wǎng)絡(luò)利用率的情況下,發(fā)送的數(shù)據(jù)與接收的數(shù)據(jù)的先后順序不能保證、mtu造成的ip分片、最大報(bào)文長度切分等!所以就出現(xiàn)了粘包、拆包的概念!常見的拆包方式有:定義分隔符、定長、定義消息頭消息體(比如http)。而我們是采用了定義分隔符的方式。再底層的數(shù)據(jù)合并已經(jīng)由操作系統(tǒng)幫我們處理了!??!
事件驅(qū)動與libev:
其實(shí)就是利用了nio的多路復(fù)用,也跟操作系統(tǒng)相關(guān)!