本文是Netty系列第4篇web
上一篇文章咱們深刻了解了I/O多路複用的三種實現形式,select/poll/epoll。編程
那Netty是使用哪一種實現的I/O多路複用呢?這個問題,得從Java NIO包提及。設計模式
Netty實際上也是一個封裝好的框架,它的網絡I/O本質上仍是使用了Java的NIO包(New IO,不是網絡I/O模型的NIO,Nonblocking IO)包。因此,從網絡I/O模型到Netty,咱們還須要瞭解下Java NIO包。瀏覽器
本文預計閱讀時間 5 分鐘,將重點回答如下幾個問題:性能優化
如何用Java NIO包實現一個服務端微信
Java NIO包如何實現I/O多路複用模型網絡
有了Java NIO包,爲何還要封裝一個Netty?多線程
1.先來看一個Java NIO服務端的例子
上一篇文章咱們已經瞭解了I/O多路複用的實現形式。
就是多個的進程的IO能夠註冊到一個複用器(selector)上,而後用一個進程調用select,select會監聽全部註冊進來的IO。併發
NIO包作了對應的實現。以下圖所示。app
有一個統一的selector負責監聽全部的Channel。這些channel中只要有一個有IO動做,就能夠經過Selector.select()方法檢測到,而且使用selectedKeys獲得這些有IO的channel,而後對它們調用相應的IO操做。
咱們來個簡單的demo作一下演示。如何使用NIO中三個核心組件(Buffer緩衝區、Channel通道、Selector選擇器)來編寫一個服務端程序。
public class NioDemo {
public static void main(String[] args) {
try {
//1.建立channel
ServerSocketChannel socketChannel1 = ServerSocketChannel.open();
//設置爲非阻塞模式,默認是阻塞的
socketChannel1.configureBlocking(false);
socketChannel1.socket().bind(new InetSocketAddress("127.0.0.1", 8811));
ServerSocketChannel socketChannel2 = ServerSocketChannel.open();
socketChannel2.configureBlocking(false);
socketChannel2.socket().bind(new InetSocketAddress("127.0.0.1", 8822));
//2.建立selector,並將channel1和channel2進行註冊。
Selector selector = Selector.open();
socketChannel1.register(selector, SelectionKey.OP_ACCEPT);
socketChannel2.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//3.一直阻塞直到有至少有一個通道準備就緒
int readChannelCount = selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//4.輪訓已經就緒的通道
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
//5.判斷準備就緒的事件類型,並做相應處理
if (key.isAcceptable()) {
// 建立新的鏈接,而且把鏈接註冊到selector上,而且聲明這個channel只對讀操做感興趣。
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer readBuff = ByteBuffer.allocate(1024);
socketChannel.read(readBuff);
readBuff.flip();
System.out.println("received : " + new String(readBuff.array()));
socketChannel.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
經過這個代碼示例,咱們能清楚地瞭解如何用Java NIO包實現一個服務端:
1)建立channel1和channel2,分別監聽特定端口。
2)建立selector,並將channel1和channel2進行註冊。
3)selector.select()一直阻塞,直到有至少有一個通道準備就緒。
4)輪訓已經就緒的通道
5)並根據事件類型作出相應的響應動做。
程序啓動後,會一直阻塞在selector.select()。
經過瀏覽器調用localhost:8811 或者 localhost:8822就能觸發咱們的服務端代碼了。
2.Java NIO包如何實現I/O多路複用模型
上文演示的Java NIO服務端已經比較清楚地展現了使用NIO編寫服務端程序的過程。
那這個過程當中如何實現了I/O多路複用的呢?
咱們得深刻看下selector的實現。
//2.建立selector,並將channel1和channel2進行註冊。
Selector selector = Selector.open();
從open這裏開始吧。
這裏用了一個SelectorProvider來建立selector。
進入SelectorProvider.provider(),看到具體的provider是由
sun.nio.ch.DefaultSelectorProvider建立的,對應的方法是:
咦?原來不一樣的操做系統會提供不一樣的provider對象。這裏包括了PollSelectorProvider、EPollSelectorProvide等。
名字是否是有點眼熟?
沒錯,跟咱們上一篇文章分析過的I/O多路複用的不一樣實現方式poll/epoll有關。
咱們選擇默認的
sun.nio.ch.PollSelectorProvider往下看看。
OK,找到了實現類PollSelectorImpl。
而後,經過如下調用:
找到最終的native方法poll0。
是否是仍然很眼熟?
沒錯!跟咱們上一篇文章分析過的poll函數是一致的。
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
繞了這麼久,到最後,仍是找到了咱們聊過I/O多路複用的 poll 實現。
至此,咱們終於把Java NIO和 I/O多路複用模型串聯起來了。
Java NIO包使用selector,實現了I/O多路複用模型。
同時,在不一樣的操做系統中,會有不一樣的poll/epoll選擇。
3.爲何還須要Netty呢?
那既然已經有了NIO包了,咱們能夠本身手動編寫服務框架了,爲何還須要封裝一個Netty框架呢?有什麼好處呢?
好處固然是有不少了!咱們從一開始實現的demo提及。
3.1 設計模式的優化
咱們的demo確實已經可以工做了,可是仍是有比較明顯的問題。第4步(輪詢已經就緒的通道)和第5步(對事件做相應處理)是在同一個線程中的,當事件處理比較耗時甚至阻塞時,整個流程就會阻塞了。
咱們使用的實際上就是 「單Reactor單線程」 設計模式。
這種模型在Reactor中負責監聽端口、接收請求,若是是鏈接事件交給acceptor處理,若是是讀寫事件和業務處理就交給handler處理,但始終只有一個線程執行全部的事情。
爲了提升性能,咱們理所固然至關能夠把事件處理交給線程池,那就能夠演進爲 「單Reactor多線程」 設計模式。
這種模型和第一種模型的主要區別是把業務處理從以前的單一線程脫離出來,換成線程池處理。Reactor線程只處理鏈接事件、讀寫事件,全部業務處理都交給線程池,充分利用多核機器的資源,提升性能。
可是這仍然不夠!
咱們能夠發現,一個Reactor線程承擔了全部的網絡事件,例如監聽和響應,高併發場景下單線程存在性能問題。
爲了充分利用多核能力,能夠構建兩個 Reactor,主 Reactor 單獨監聽server socket,accept新鏈接,而後將創建的 SocketChannel 註冊給指定的從 Reactor,從Reactor再執行事件的讀寫、分發,把業務處理就扔給worker線程池完成。這就演進爲 」主從Reactor模式「 設計模式。
因此,若是有人直接幫咱們 封裝好這樣的設計模式 ,是否是太好了?
沒錯,Netty就是這樣的「活雷鋒」!
Netty就使用了主從Reactor模式封裝了Java NIO包的使用,大大提升了性能。
3.2 其餘優勢 (之後的核心知識點)
除了封裝了高性能的設計模式外,Netty還有許多其餘優勢:
穩定性。 Netty 更加可靠穩定,修復和完善了 JDK NIO 較多已知問題,包括 select 空轉致使 CPU 消耗 100%、keep-alive 檢測等問題。
性能優化。對象池複用技術。Netty 經過複用對象,避免頻繁建立和銷燬帶來的開銷。零拷貝技術。 除了操做系統級別的零拷貝技術外,Netty 提供了面向用戶態的零拷貝技術,在 I/O 讀寫時直接使用 DirectBuffer,避免了數據在堆內存和堆外內存之間的拷貝。
便捷性。 Netty 提供了不少經常使用的工具,例如行解碼器、長度域解碼器等。若是咱們使用JDK NIO包,那麼這些經常使用工具都須要本身進行實現。
正是由於 Netty 作到了高性能、高穩定性、高易用性,完美彌補了 Java NIO 的不足,因此在咱們在網絡編程時,首選Netty,而不是本身直接使用Java NIO。
回顧一下前幾章內容,到目前爲止,咱們從網絡I/O模型出發,一步步瞭解到了Netty的網絡I/O模型。
對於I/O多路複用、Java NIO包 和 Netty 的關係也有了全面的認識。
有了這些知識基礎,咱們初步瞭解了Netty是什麼,爲何使用Netty。
後面的文章,咱們將逐步展開Netty框架的核心知識點,敬請期待。
往期熱門筆記合集推薦:
原創:阿丸筆記(微信公衆號:aone_note),歡迎 分享,轉載請保留出處。
掃描下方二維碼能夠關注我哦~
若是有任何疑問或者建議,歡迎 寫留言 或者 微信 和我聯繫哦~
本文分享自微信公衆號 - 阿丸筆記(aone_note)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。