自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

開發(fā) | Netty快速入門,一看就懂!

開發(fā) 架構(gòu) 開發(fā)工具
很早以前就寫過關(guān)于 Netty 的使用,最近發(fā)現(xiàn)還有網(wǎng)友在看之前寫的那篇 Netty 文章,個人感覺那時候?qū)懙暮艽植冢掠绊懲械拈喿x質(zhì)量,所以決定重新寫一些關(guān)于 Netty 的文章,補充以前的不足。

很早以前就寫過關(guān)于 Netty 的使用,最近發(fā)現(xiàn)還有網(wǎng)友在看之前寫的那篇 Netty 文章,個人感覺那時候?qū)懙暮艽植?,怕影響同行的閱讀質(zhì)量,所以決定重新寫一些關(guān)于 Netty 的文章,補充以前的不足。

[[322250]]

圖片來自 Pexels

Netty 能做啥

簡單說就是用來處理網(wǎng)絡(luò)編程,寫一款能進(jìn)行網(wǎng)絡(luò)通信的服務(wù)端和客戶端程序。如果沒有 Netty,在 Java 的世界中如何處理網(wǎng)絡(luò)編程呢?

Java 自帶的工具有:java.net 包,用于處理網(wǎng)絡(luò)通信,后面 Java 提供了 NIO 工具包用于提供非阻塞的通信。

與 Netty 同級別的第三方工具包:Mina,在設(shè)計上與 Netty 有些許不同,但是核心都是提供網(wǎng)絡(luò)通信的能力。

傳統(tǒng)網(wǎng)絡(luò)通信模型

說 Netty 之前還是先講一下傳統(tǒng)的網(wǎng)絡(luò)編程是什么樣子。傳統(tǒng)的 Socket 編程開發(fā)步驟很簡單,只需要使用 Socket 類創(chuàng)建客戶端和服務(wù)端即可。

但是為啥現(xiàn)在沒有人用它了呢?主要原因是它基于同步阻塞 IO 的線程模型去做的,在當(dāng)今時代完全不能滿足生產(chǎn)需要,自然被 Out。

同步阻塞 IO 模型

同步阻塞線程模型的問題在于一個請求必須綁定一個線程去處理,并且所有的請求都是同步操作,意味著該請求未處理完之前這個連接不會被釋放,如果并發(fā)高的情況必然會導(dǎo)致系統(tǒng)壓力過大。

Netty 的新線程模型

基于此,Java 新增了非阻塞的 IO 操作包 NIO, NIO 的線程模型采用了 Reactor 模式,即異步非阻塞的方式,解決了之前同步阻塞帶來的問題。

NIO 的全稱是 NoneBlocking IO,非阻塞 IO,區(qū)別與 BIO,BIO 的全稱是 Blocking IO,阻塞 IO。

那這個阻塞是什么意思呢?

  • Accept 是阻塞的,只有新連接來了,Accept 才會返回,主線程才能繼。
  • Read 是阻塞的,只有請求消息來了,Read 才能返回,子線程才能繼續(xù)處理。
  • Write 是阻塞的,只有客戶端把消息收了,Write 才能返回,子線程才能繼續(xù)讀取下一個請求。

服務(wù)器在處理響應(yīng)的設(shè)計模式方面目前主要分為兩種:

  • 線程驅(qū)動
  • 事件驅(qū)動

同步阻塞就是線程驅(qū)動的模式,最明顯的例子就是 Tomcat;對于事件驅(qū)動來說,沒有必要為每一個連接都創(chuàng)建一個線程去維護(hù)。

參考觀察者模式,可以設(shè)置一個事件池,用一個單線程去循環(huán)監(jiān)聽當(dāng)前池中是否有完成的事件,如果有則取出該事件。

簡單說一下 Reactor 模式是如何解決線程等待問題的:在等待 IO 的時候,線程可以先退出不用一直等待 IO 操作。

但是如果不等待那么 IO 處理完成之后返回給誰呢?Reactor 模型采用了事件驅(qū)動機制,要求線程在退出前向 event loop 注冊回調(diào)函數(shù),這樣 IO 完成之后 event loop 就可以調(diào)用回調(diào)函數(shù)完成數(shù)據(jù)返回。

在 Reactor 中有 4 個角色,所有的數(shù)據(jù)流入的處理統(tǒng)一稱為 Channel,就像是一個水管,Reactor 模型將每一種事件拆分為一個 event,相同類型的 event 歸為一類,這一類的統(tǒng)一處理邏輯被稱為一個 handler。

那么怎么去讓一個或者多個線程去監(jiān)聽所有的 Channel 呢?所以就有 Selector。

Selector 就像是一個管理者,你可以將多個 Channel 注冊到 一個 Selector 線程上,它會使用一個阻塞方法去捕獲當(dāng)前 Channel 上是否有事件發(fā)生,如果有則取出事件交給對應(yīng)的 Handler 去處理。

Netty 是建立在 NIO 之上的,并且 Netty 在 NIO 上面又提供了更多高層次 API 的封裝。

為什么不用 JDK 提供的 NIO

JDK 已經(jīng)給我們提供了 NIO 的包,也是使用了 Reactor 模型來實現(xiàn)的異步非阻塞模式,那我們?yōu)樯对谌粘i_發(fā)中沒有聽到誰直接使用 NIO 來開發(fā)網(wǎng)絡(luò)編程呢?

實際上大家不使用的原因是因為它太難控制。Java NIO 類庫中主要提供的功能包括:

  • 緩沖區(qū) Buffer
  • 通道 Channel
  • 多路復(fù)用器 Selector

緩沖區(qū) Buffer 其實就是一個對象,即所有流入或者流出的數(shù)據(jù)都在 Buffer 中存在。

新 IO 與老的面向流 IO 的區(qū)別在于老 IO 直接面向字節(jié)流進(jìn)行處理,新 IO 是面向緩沖區(qū)進(jìn)行處理,讀寫數(shù)據(jù)都是先讀寫到緩沖區(qū)中。

緩沖區(qū)實質(zhì)上是一個字節(jié)數(shù)組,NIO 提供了對緩沖區(qū)數(shù)據(jù)讀寫位置維護(hù)的操作能力。

Buffer 和 Channel 的關(guān)系

Channel 通道,所有 Buffer 內(nèi)的數(shù)據(jù)都會往 Channel 上流,數(shù)據(jù)通過 Channel 流向處理邏輯,通過 Channel 將處理過的數(shù)據(jù)返回給客戶端。

所以 Channel 是全雙工的,可以支持讀寫,這是它與 Stream 的區(qū)別。如果你使用 Stream,讀數(shù)據(jù)只能使用 InputStream 進(jìn)行操作,寫數(shù)據(jù)只能使用 OutputStream 進(jìn)行操作。

用現(xiàn)實世界中的事物比喻的話,傳統(tǒng) IO 猶如水管,水流只能沿著管道往下流;NIO 猶如一條雙向公路,兩個方向都可以行車。

另外也正是因為 Buffer 的引入我們才能隨意的控制每次傳輸讀多少數(shù)據(jù),如果上次讀取失敗,那么應(yīng)該從多少偏移量重新讀取,這是傳統(tǒng) I/O 流無法比擬的。

Selector 選擇器,它是 NIO 的核心,一個 Selector 就是一個線程,NIO 允許一個 Selector 管理多個 Channel,即將 Channel 注冊到 Selector 上。

Selector 會去監(jiān)聽注冊的 Channel 上是否有事件準(zhǔn)備就緒,如果有就取出處理。

Selector 的輪詢監(jiān)聽

關(guān)于 NIO 的代碼我就不寫了,是很龐大的一堆,大家百度一下就能看到??傊谶@個思想來進(jìn)行網(wǎng)絡(luò)編程肯定是面對當(dāng)今流量洪峰的最佳方式。而正好 Netty 底層基于 NIO 去做的封裝,已經(jīng)給你屏蔽了這一大坨操作。

網(wǎng)絡(luò)編程還有一個問題就是跨平臺性,NIO 底層是依賴系統(tǒng)的 IO API,不同的系統(tǒng)可能對 IO API 的實現(xiàn)也是不一樣的,這里如何你使用 NIO 那么就需要考慮系統(tǒng)兼容性問題了。

另外還有一個問題就是 NIO 有個很著名的 bug,JDK 的 NIO 底層由 epoll 實現(xiàn),若 Selector 的輪詢結(jié)果為空,也沒有 wakeup 或新消息處理,則發(fā)生空輪詢,CPU 使用率 100%。

這個 Bug 官方聲明已經(jīng)修復(fù),事實上沒有被 Fix, 只是出現(xiàn)的概率會降低一些。

Netty 也對該 Bug 進(jìn)行了處理:對 Selector 的 Select 操作周期進(jìn)行統(tǒng)計,每完成一次空的 Select 操作進(jìn)行一次計數(shù),若在某個周期內(nèi)連續(xù)發(fā)生 N 次空輪詢,則觸發(fā)了 Epoll 死循環(huán) Bug。

那么這個時候就重建 Selector,判斷是否是其他線程發(fā)起的重建請求,若不是則將原 SocketChannel 從舊的 Selector 上去除注冊,重新注冊到新的 Selector 上,并將原來的 Selector 關(guān)閉。

網(wǎng)絡(luò)編程應(yīng)該注意什么

既然說要學(xué)習(xí) Netty, 它本身是基于 NIO 的封裝用于網(wǎng)絡(luò)通信,那么在編寫一段用于網(wǎng)絡(luò)通信的代碼我們應(yīng)該注意一些什么呢?弄清楚這些問題,我們大概就知道 Netty 都做了什么。

談到網(wǎng)絡(luò)就不能避免說到 OSI 7 層模型和 TCP / IP 4 層模型:

OSI 模型和對應(yīng)的網(wǎng)絡(luò)協(xié)議

Java 網(wǎng)絡(luò)編程主要使用的是 Socket 套接字編程,基于 4 層協(xié)議的網(wǎng)絡(luò)編程,即基于 TCP/ UDP 協(xié)議的封裝。

編寫一個 Socket 通信都有哪些步驟呢?

  • 創(chuàng)建一個 ServerSocket,監(jiān)聽并綁定一個端口。
  • 一系列客戶端來請求這個端口。
  • 服務(wù)器使用 Accept,獲得一個來自客戶端的 Socket 連接對象。
  • 啟動一個新線程處理連接:①讀 Socket,得到字節(jié)流;②解碼協(xié)議,得到 HTTP 請求對象;③處理 HTTP 請求,得到一個結(jié)果,封裝成一個 HTTPResponse 對象;④編碼協(xié)議,將結(jié)果序列化字節(jié)流;④寫 Socket,將字節(jié)流發(fā)給客戶端。
  • 繼續(xù)循環(huán)步驟 3。

根據(jù)以上的數(shù)據(jù)傳輸流程,我們可以提出一些問題:

  • 如何約定字節(jié)流長度格式,以保證每次讀到的字節(jié)流都是最新的而不會和上次重復(fù)。
  • 傳輸字節(jié)流的編解碼問題。
  • 一個服務(wù)端肯定會有多個客戶端鏈接,如何管理眾多的客戶端鏈接,比如如何維護(hù)斷線重連,連接超時以及關(guān)閉機制。

上面這些問題我們在接下來的 Netty 學(xué)習(xí)中都會找到答案。

Netty 核心組件

在還未入門 Netty 之前我們先了解一下 Netty 里面都有哪些類,做到有的放矢,后面學(xué)習(xí)帶著這些關(guān)鍵信息不回亂。

①Bootstrap、ServerBootstrap

一個 Netty 應(yīng)用通常由一個 Bootstrap 開始,主要作用是配置整個 Netty 程序,串聯(lián)各個組件,Netty 中 Bootstrap 類是客戶端程序的啟動引導(dǎo)類,ServerBootstrap 是服務(wù)端啟動引導(dǎo)類。

②Future、ChannelFuture

在 Netty 中所有的 IO 操作都是異步的,不會立刻知道某個事件是否完成處理。

但是可以過一會等它執(zhí)行完成或者直接注冊一個監(jiān)聽,具體的實現(xiàn)就是通過 Future 和 ChannelFutures,用來注冊一個監(jiān)聽,當(dāng)操作執(zhí)行成功或失敗時監(jiān)聽會自動觸發(fā)注冊的監(jiān)聽事件。

③Channel

Netty 網(wǎng)絡(luò)通信的組件,能夠用于執(zhí)行網(wǎng)絡(luò) I/O 操作。

Channel 為用戶提供:

  • 當(dāng)前網(wǎng)絡(luò)連接的通道的狀態(tài)(例如是否打開,是否已連接)。
  • 網(wǎng)絡(luò)連接的配置參數(shù) (例如接收緩沖區(qū)大小)。
  • 提供異步的網(wǎng)絡(luò) I/O 操作(如建立連接,讀寫,綁定端口),異步調(diào)用意味著任何 I/O 調(diào)用都將立即返回,并且不保證在調(diào)用結(jié)束時所請求的 I/O 操作已完成。
  • 調(diào)用立即返回一個 ChannelFuture 實例,通過注冊監(jiān)聽器到 ChannelFuture 上,可以 I/O 操作成功、失敗或取消時回調(diào)通知調(diào)用方。
  • 支持關(guān)聯(lián) I/O 操作與對應(yīng)的處理程序。

不同協(xié)議、不同的阻塞類型的連接都有不同的 Channel 類型與之對應(yīng)。

下面是一些常用的 Channel 類型:

  • NioSocketChannel,異步的客戶端 TCP Socket 連接。
  • NioServerSocketChannel,異步的服務(wù)器端 TCP Socket 連接。
  • NioDatagramChannel,異步的 UDP 連接。
  • NioSctpChannel,異步的客戶端 Sctp 連接。
  • NioSctpServerChannel,異步的 Sctp 服務(wù)器端連接,這些通道涵蓋了 UDP 和 TCP 網(wǎng)絡(luò) IO 以及文件 IO。

④Selector

Netty 基于 Selector 對象實現(xiàn) I/O 多路復(fù)用,通過 Selector 一個線程可以監(jiān)聽多個連接的 Channel 事件。

當(dāng)向一個 Selector 中注冊 Channel 后,Selector 內(nèi)部的機制就可以自動不斷地查詢(Select)這些注冊的 Channel 是否有已就緒的 I/O 事件(例如可讀,可寫,網(wǎng)絡(luò)連接完成等),這樣程序就可以很簡單地使用一個線程高效地管理多個 Channel 。

⑤NioEventLoop

NioEventLoop 中維護(hù)了一個線程和任務(wù)隊列,支持異步提交執(zhí)行任務(wù),線程啟動時會調(diào)用 NioEventLoop 的 run 方法,執(zhí)行 I/O 任務(wù)和非 I/O 任務(wù):

  • I/O 任務(wù),即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法觸發(fā)。
  • 非 IO 任務(wù),添加到 taskQueue 中的任務(wù),如 register0、bind0 等任務(wù),由 runAllTasks 方法觸發(fā)。

兩種任務(wù)的執(zhí)行時間比由變量 ioRatio 控制,默認(rèn)為 50,則表示允許非 IO 任務(wù)執(zhí)行的時間與 IO 任務(wù)的執(zhí)行時間相等。

⑥NioEventLoopGroup

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解為一個線程池,內(nèi)部維護(hù)了一組線程,每個線程(NioEventLoop)負(fù)責(zé)處理多個 Channel 上的事件,而一個 Channel 只對應(yīng)于一個線程。

⑦ChannelHandler

ChannelHandler 是一個接口,處理 I/O 事件或攔截 I/O 操作,并將其轉(zhuǎn)發(fā)到其 ChannelPipeline(業(yè)務(wù)處理鏈)中的下一個處理程序。

ChannelHandler 本身并沒有提供很多方法,因為這個接口有許多的方法需要實現(xiàn),方便使用期間,可以繼承它的子類:

  • ChannelInboundHandler 用于處理入站 I/O 事件。
  • ChannelOutboundHandler 用于處理出站 I/O 操作。

或者使用以下適配器類:

  • ChannelInboundHandlerAdapter 用于處理入站 I/O 事件。
  • ChannelOutboundHandlerAdapter 用于處理出站 I/O 操作。
  • ChannelDuplexHandler 用于處理入站和出站事件。

⑧ChannelHandlerContext

保存 Channel 相關(guān)的所有上下文信息,同時關(guān)聯(lián)一個 ChannelHandler 對象。

⑨ChannelPipline

保存 ChannelHandler 的 List,用于處理或攔截 Channel 的入站事件和出站操作。

它實現(xiàn)了一種高級形式的攔截過濾器模式,使用戶可以完全控制事件的處理方式,以及 Channel 中各個的 ChannelHandler 如何相互交互。

在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應(yīng)。

關(guān)于 Netty 的簡介就先說這么多,后面會帶著 Socket 通信應(yīng)該解決的問題和上面提到的 Netty 關(guān)鍵組件講解 Netty 是如何實現(xiàn)高性能網(wǎng)絡(luò)通信的。

作者:rickiyang

編輯:陶家龍

出處:https://www.cnblogs.com/rickiyang/

 

責(zé)任編輯:武曉燕 來源: 博客園
相關(guān)推薦

2021-12-30 09:10:28

游戲開發(fā)開發(fā)技術(shù)熱點

2023-05-12 09:08:48

TypeScript工具類型

2020-03-27 09:06:54

選擇排序算法冒泡排序

2020-09-21 08:33:12

線程池調(diào)度Thread Pool

2021-05-14 07:11:49

方法調(diào)用類加載

2015-07-21 13:07:14

Reactjs教程

2018-09-28 14:28:28

MySQL存儲過程

2019-08-14 10:20:32

算法數(shù)組鏈表

2021-07-15 09:55:47

systemdLinux文件

2019-01-15 09:55:24

RAID磁盤陣列數(shù)據(jù)存儲

2022-08-15 19:49:57

Consul架構(gòu)注冊中心

2022-05-29 22:55:00

適配器設(shè)計模式

2020-05-09 14:40:29

UI設(shè)計開發(fā)

2024-12-12 08:22:03

負(fù)載均衡算法無狀態(tài)

2025-03-04 02:00:00

Python編寫自動化

2024-11-20 16:02:47

.NET 9LINQ開發(fā)

2021-01-07 10:30:23

設(shè)計模式

2021-05-13 07:30:27

Kafka消息流系統(tǒng)

2019-08-22 09:22:44

數(shù)據(jù)結(jié)構(gòu)二叉搜索樹

2021-06-01 06:01:35

SSO單點登錄
點贊
收藏

51CTO技術(shù)棧公眾號