文章來(lái)源知乎,作者:老錢(qián)
作為一個(gè)學(xué)Java的煞肾,如果沒(méi)有研究過(guò)Netty咧织,那么你對(duì)Java語(yǔ)言的使用和理解僅僅停留在表面水平,會(huì)點(diǎn)SSH籍救,寫(xiě)幾個(gè)MVC习绢,訪問(wèn)數(shù)據(jù)庫(kù)和緩存,這些只是初等Java程序員干的事蝙昙。如果你要進(jìn)階闪萄,想了解Java服務(wù)器的深層高階知識(shí),Netty絕對(duì)是一個(gè)必須要過(guò)的門(mén)檻奇颠。
有了Netty败去,你可以實(shí)現(xiàn)自己的HTTP服務(wù)器,F(xiàn)TP服務(wù)器大刊,UDP服務(wù)器为迈,RPC服務(wù)器,WebSocket服務(wù)器缺菌,Redis的Proxy服務(wù)器,MySQL的Proxy服務(wù)器等等搜锰。
如果你想知道Nginx是怎么寫(xiě)出來(lái)的伴郁,如果你想知道Tomcat和Jetty是如何實(shí)現(xiàn)的,如果你也想實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Redis服務(wù)器蛋叼,那都應(yīng)該好好理解一下Netty焊傅,它們高性能的原理都是類(lèi)似的剂陡。
1. 創(chuàng)建一個(gè)ServerSocket,監(jiān)聽(tīng)并綁定一個(gè)端口
2. 一系列客戶(hù)端來(lái)請(qǐng)求這個(gè)端口
3. 服務(wù)器使用Accept狐胎,獲得一個(gè)來(lái)自客戶(hù)端的Socket連接對(duì)象
4. 啟動(dòng)一個(gè)新線(xiàn)程處理連接
4.1 讀Socket鸭栖,得到字節(jié)流
4.2 解碼協(xié)議,得到Http請(qǐng)求對(duì)象
4.3 處理Http請(qǐng)求握巢,得到一個(gè)結(jié)果晕鹊,封裝成一個(gè)HttpResponse對(duì)象
4.4 編碼協(xié)議,將結(jié)果序列化字節(jié)流
4.5 寫(xiě)Socket暴浦,將字節(jié)流發(fā)給客戶(hù)端繼續(xù)
5. 循環(huán)步驟3
HTTP服務(wù)器之所以稱(chēng)為HTTP服務(wù)器溅话,是因?yàn)榫幋a解碼協(xié)議是HTTP協(xié)議,如果協(xié)議是Redis協(xié)議歌焦,那它就成了Redis服務(wù)器飞几,如果協(xié)議是WebSocket,那它就成了WebSocket服務(wù)器独撇,等等屑墨。
使用Netty你就可以定制編解碼協(xié)議,實(shí)現(xiàn)自己的特定協(xié)議的服務(wù)器纷铣。
上面我們說(shuō)的是一個(gè)傳統(tǒng)的多線(xiàn)程服務(wù)器绪钥,這個(gè)也是Apache處理請(qǐng)求的模式。在高并發(fā)環(huán)境下关炼,線(xiàn)程數(shù)量可能會(huì)創(chuàng)建太多程腹,操作系統(tǒng)的任務(wù)調(diào)度壓力大,系統(tǒng)負(fù)載也會(huì)比較高儒拂。那怎么辦呢寸潦?
于是NIO誕生了,NIO并不是Java獨(dú)有的概念社痛,NIO代表的一個(gè)詞匯叫著IO多路復(fù)用见转。它是由操作系統(tǒng)提供的系統(tǒng)調(diào)用,早期這個(gè)操作系統(tǒng)調(diào)用的名字是select蒜哀,但是性能低下斩箫,后來(lái)漸漸演化成了Linux下的epoll和Mac里的kqueue。我們一般就說(shuō)是epoll撵儿,因?yàn)闆](méi)有人拿蘋(píng)果電腦作為服務(wù)器使用對(duì)外提供服務(wù)乘客。而Netty就是基于Java NIO技術(shù)封裝的一套框架。為什么要封裝淀歇,因?yàn)樵腏ava NIO使用起來(lái)沒(méi)那么方便易核,而且還有臭名昭著的bug,Netty把它封裝之后浪默,提供了一個(gè)易于操作的使用模式和接口牡直,用戶(hù)使用起來(lái)也就便捷多了缀匕。
那NIO究竟是什么東西呢?
NIO的全稱(chēng)是NoneBlocking IO碰逸,非阻塞IO乡小,區(qū)別與BIO,BIO的全稱(chēng)是Blocking IO饵史,阻塞IO满钟。那這個(gè)阻塞是什么意思呢?
- Accept是阻塞的约急,只有新連接來(lái)了零远,Accept才會(huì)返回,主線(xiàn)程才能繼
- Read是阻塞的厌蔽,只有請(qǐng)求消息來(lái)了牵辣,Read才能返回,子線(xiàn)程才能繼續(xù)處理
- Write是阻塞的奴饮,只有客戶(hù)端把消息收了纬向,Write才能返回,子線(xiàn)程才能繼續(xù)讀取下一個(gè)請(qǐng)求
所以傳統(tǒng)的多線(xiàn)程服務(wù)器是BlockingIO模式的戴卜,從頭到尾所有的線(xiàn)程都是阻塞的逾条。這些線(xiàn)程就干等在哪里,占用了操作系統(tǒng)的調(diào)度資源投剥,什么事也不干师脂,是浪費(fèi)。
么NIO是怎么做到非阻塞的呢江锨。它用的是事件機(jī)制吃警。它可以用一個(gè)線(xiàn)程把Accept,讀寫(xiě)操作啄育,請(qǐng)求處理的邏輯全干了酌心。如果什么事都沒(méi)得做,它也不會(huì)死循環(huán)挑豌,它會(huì)將線(xiàn)程休眠起來(lái)安券,直到下一個(gè)事件來(lái)了再繼續(xù)干活,這樣的一個(gè)線(xiàn)程稱(chēng)之為NIO線(xiàn)程氓英。
while true {
events = takeEvents(fds) // 獲取事件侯勉,如果沒(méi)有事件,線(xiàn)程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新鏈接來(lái)了
} elif event.isReadable {
request = doRead() // 讀消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 寫(xiě)消息
}
}
}
NIO的流程大致就是上面的偽代碼描述的過(guò)程债蓝,跟實(shí)際真實(shí)的代碼有較多差異壳鹤,不過(guò)對(duì)于初學(xué)者,這樣理解也是足夠了饰迹。
Netty是建立在NIO基礎(chǔ)之上芳誓,Netty在NIO之上又提供了更高層次的抽象。
在Netty里面啊鸭,Accept連接可以使用單獨(dú)的線(xiàn)程池去處理锹淌,讀寫(xiě)操作又是另外的線(xiàn)程池來(lái)處理。
Accept連接和讀寫(xiě)操作也可以使用同一個(gè)線(xiàn)程池來(lái)進(jìn)行處理赠制。而請(qǐng)求處理邏輯既可以使用單獨(dú)的線(xiàn)程池進(jìn)行處理赂摆,也可以跟放在讀寫(xiě)線(xiàn)程一塊處理。線(xiàn)程池中的每一個(gè)線(xiàn)程都是NIO線(xiàn)程钟些。用戶(hù)可以根據(jù)實(shí)際情況進(jìn)行組裝烟号,構(gòu)造出滿(mǎn)足系統(tǒng)需求的并發(fā)模型。
Netty提供了內(nèi)置的常用編解碼器政恍,包括行編解碼器[一行一個(gè)請(qǐng)求]汪拥,前綴長(zhǎng)度編解碼器[前N個(gè)字節(jié)定義請(qǐng)求的字節(jié)長(zhǎng)度],可重放解碼器[記錄半包消息的狀態(tài)]篙耗,HTTP編解碼器迫筑,WebSocket消息編解碼器等等
Netty提供了一些列生命周期回調(diào)接口,當(dāng)一個(gè)完整的請(qǐng)求到達(dá)時(shí)宗弯,當(dāng)一個(gè)連接關(guān)閉時(shí)脯燃,當(dāng)一個(gè)連接建立時(shí),用戶(hù)都會(huì)收到回調(diào)事件蒙保,然后進(jìn)行邏輯處理辕棚。
Netty可以同時(shí)管理多個(gè)端口,可以使用NIO客戶(hù)端模型邓厕,這些對(duì)于RPC服務(wù)是很有必要的逝嚎。
Netty除了可以處理TCP Socket之外,還可以處理UDP Socket邑狸。在消息讀寫(xiě)過(guò)程中懈糯,需要大量使用ByteBuffer,Netty對(duì)ByteBuffer在性能和使用的便捷性上都進(jìn)行了優(yōu)化和抽象单雾。
總之赚哗,Netty是Java程序員進(jìn)階的必備神奇。如果你知其然硅堆,還想知其所以然屿储,一定要好好研究下Netty。如果你覺(jué)得Java枯燥無(wú)謂渐逃,Netty則是重新開(kāi)啟你對(duì)Java興趣大門(mén)的鑰匙够掠。
讀完一整篇也許你對(duì)Netty還不是特別清晰,可以關(guān)注公眾號(hào)「八戒程序猿」茄菊,發(fā)送「netty教程」即可獲取一套讓你少花時(shí)間疯潭,少走彎路的 netty教程赊堪。