摘要:最近在學習網絡編程和相關的知識,了解到是模式的網絡框架,但是提供了不同的來支持不同模式的網絡通信處理,包括同步異步阻塞和非阻塞。因為的版本使用的的模式,而則希望使用模式,而且版本沒有將的部分配置項暴露出來,比如說和。
??最近在學習Java網絡編程和Netty相關的知識,了解到Netty是NIO模式的網絡框架,但是提供了不同的Channel來支持不同模式的網絡通信處理,包括同步、異步、阻塞和非阻塞。學習要從基礎開始,所以我們就要先了解一下相關的基礎概念和Java原生的NIO。這里,就將最近我學習的知識總結一下,以供大家了解。
?為了節約你的時間,本文主要內容如下:
異步,阻塞的概念
操作系統I/O的類型
Java NIO的底層實現
異步,同步,阻塞,非阻塞?同步和異步關注的是消息通信機制,所謂同步就是調用者進行調用后,在沒有得到結果之前,該調用一直不會返回,但是一旦調用返回,就得到了返回值,同步就是指調用者主動等待調用結果;而異步則相反,執行調用之后直接返回,所以可能沒有返回值,等到有返回值時,由被調用者通過狀態,通知來通知調用者.異步就是指被調用者來通知調用者調用結果就緒.所以,二者在消息通信機制上有所不同,一個是調用者檢查調用結果是否就緒,一個是被調用者通知調用者結果就緒
?阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.阻塞調用是指在調用結果返回之前,當前線程會被掛起,調用線程只有在得到結果之后才會繼續執行.非阻塞調用是指在不能立刻得到結構之前,調用線程不會被掛起,還是可以執行其他事情.
?兩組概念相互組合就有四種情況,分別是同步阻塞,同步非阻塞,異步阻塞,異步非阻塞.我們來舉個例子來分別類比上訴四種情況.
?比如你要從網上下載一個1G的文件,按下下載按鈕之后,如果你一直在電腦旁邊,等待下載結束,這種情況就是同步阻塞;如果你不需要一直呆在電腦旁邊,你可以去看一會書,但是你還是隔一段時間來查看一下下載進度,這種情況就是同步非阻塞;如果你一直在電腦旁邊,但是下載器在下載結束之后會響起音樂來提醒你,這就是異步阻塞;但是如果你不呆在電腦旁邊,去看書,下載器下載結束后響起音樂來提醒你,那么這種情況就是異步非阻塞.
?知道上述兩組概念之后,我們來看一下Unix下可用的5種I/O模型:
阻塞I/O(bloking IO)
非阻塞I/O(nonblocking IO)
多路復用I/O(IO multiplexing)
信號驅動I/O(signal driven IO)
異步I/O(asynchronous IO)
?前4種都是同步,只有最后一種是異步I/O.需要注意的是Java NIO依賴于Unix系統的多路復用I/O,對于I/O操作來說,它是同步I/O,但是對于編程模型來說,它是異步網絡調用.下面我們就以系統read的調用來介紹不同的I/O類型.
?當一個read發生時,它會經歷兩個階段:
1 等待數據準備
2 將數據從內核內存空間拷貝到進程內存空間中
?不同的I/O類型,在這兩個階段中有不同的行為.但是由于這塊內容比較多,而且多為表述性的知識,所以這里我們只給出幾張圖片來解釋,感覺興趣的同學可以去具體了解一下。
Java NIO的底層實現?我們都知道Netty通過JNI的方式提供了Native Socket Transport,為什么Netty要提供自己的Native版本的NIO呢?明明Java NIO底層也是基于epoll調用(最新的版本)的.這里,我們先不明說,大家想一想可能的情況.下列的源碼都來自于OpenJDK-8u40-b25版本.
open方法?如果我們順著Selector.open()方法一個類一個類的找下去,很容易就發現Selector的初始化是由DefaultSelectorProvider根據不同操作系統平臺生成的不同的SelectorProvider,對于Linux系統,它會生成EPollSelectorProvider實例,而這個實例會生成EPollSelectorImpl作為最終的Selector實現.
class EPollSelectorImpl extends SelectorImpl { ..... // The poll object EPollArrayWrapper pollWrapper; ..... EPollSelectorImpl(SelectorProvider sp) throws IOException { ..... pollWrapper = new EPollArrayWrapper(); pollWrapper.initInterrupt(fd0, fd1); ..... } ..... }
?EpollArrayWapper將Linux的epoll相關系統調用封裝成了native方法供EpollSelectorImpl使用.
private native int epollCreate(); private native void epollCtl(int epfd, int opcode, int fd, int events); private native int epollWait(long pollAddress, int numfds, long timeout, int epfd) throws IOException;
?上述三個native方法就對應Linux下epoll相關的三個系統調用
//創建一個epoll句柄,size是這個監聽的數目的最大值. int epoll_create(int size); //事件注冊函數,告訴內核epoll監聽什么類型的事件,參數是感興趣的事件類型,回調和監聽的fd int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //等待事件的產生,類似于select調用,events參數用來從內核得到事件的集合 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
?所以,我們會發現在EpollArrayWapper的構造函數中調用了epollCreate方法,創建了一個epoll的句柄.這樣,Selector對象就算創造完畢了.
register方法?與open類似,ServerSocketChannel的register函數底層是調用了SelectorImpl類的register方法,這個SelectorImpl就是EPollSelectorImpl的父類.
protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) { if (!(ch instanceof SelChImpl)) throw new IllegalSelectorException(); //生成SelectorKey來存儲到hashmap中,一共之后獲取 SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this); //attach用戶想要存儲的對象 k.attach(attachment); //調用子類的implRegister方法 synchronized (publicKeys) { implRegister(k); } //設置關注的option k.interestOps(ops); return k; }
?EpollSelectorImpl的相應的方法實現如下,它調用了EPollArrayWrapper的add方法,記錄下Channel所對應的fd值,然后將ski添加到keys變量中.在EPollArrayWrapper中有一個byte數組eventLow記錄所有的channel的fd值.
protected void implRegister(SelectionKeyImpl ski) { if (closed) throw new ClosedSelectorException(); SelChImpl ch = ski.channel; //獲取Channel所對應的fd,因為在linux下socket會被當作一個文件,也會有fd int fd = Integer.valueOf(ch.getFDVal()); fdToKey.put(fd, ski); //調用pollWrapper的add方法,將channel的fd添加到監控列表中 pollWrapper.add(fd); //保存到HashSet中,keys是SelectorImpl的成員變量 keys.add(ski); }
?我們會發現,調用register方法并沒有涉及到EpollArrayWrapper中的native方法epollCtl的調用,這是因為他們將這個方法的調用推遲到Select方法中去了.
Select方法?和register方法類似,SelectorImpl中的select方法最終調用了其子類EpollSelectorImpl的doSelect方法
protected int doSelect(long timeout) throws IOException { ..... try { .... //調用了poll方法,底層調用了native的epollCtl和epollWait方法 pollWrapper.poll(timeout); } finally { .... } .... //更新selectedKeys,為之后的selectedKeys函數做準備 int numKeysUpdated = updateSelectedKeys(); .... return numKeysUpdated; }
?由上述的代碼,可以看到,EPollSelectorImpl先調用EPollArrayWapper的poll方法,然后在更新SelectedKeys.其中poll方法會先調用epollCtl來注冊先前在register方法中保存的Channel的fd和感興趣的事件類型,然后epollWait方法等待感興趣事件的生成,導致線程阻塞.
int poll(long timeout) throws IOException { updateRegistrations(); ////先調用epollCtl,更新關注的事件類型 ////導致阻塞,等待事件產生 updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd); ..... return updated; }
?等待關注的事件產生之后(或在等待時間超過預先設置的最大時間),epollWait函數就會返回.select函數從阻塞狀態恢復.
selectedKeys方法?我們先來看SelectorImpl中的selectedKeys方法.
//是通過Util.ungrowableSet生成的,不能添加,只能減少 private SetpublicSelectedKeys; public Set selectedKeys() { .... return publicSelectedKeys; }
?很奇怪啊,怎麼直接就返回publicSelectedKeys了,難道在select函數的執行過程中有修改過這個變量嗎?
?publicSelectedKeys這個對象其實是selectedKeys變量的一份副本,你可以在SelectorImpl的構造函數中找到它們倆的關系,我們再回頭看一下select中updateSelectedKeys方法.
private int updateSelectedKeys() { //更新了的keys的個數,或在說是產生的事件的個數 int entries = pollWrapper.updated; int numKeysUpdated = 0; for (int i=0; i后記 ?看到這里,詳細大家都已經了解到了NIO的底層實現了吧.這里我想在說兩個問題.
?一是為什么Netty自己又從新實現了一邊native相關的NIO底層方法? 聽聽Netty的創始人是怎麼說的吧鏈接。因為Java的版本使用的epoll的level-triggered模式,而Netty則希望使用edge-triggered模式,而且Java版本沒有將epoll的部分配置項暴露出來,比如說TCP_CORK和SO_REUSEPORT。
?二是看這么多源碼,花費這么多時間有什么作用呢?我感覺如果從非功利的角度來看,那么就是純粹的希望了解的更多,有時候看完源碼或在理解了底層原理之后,都會用一種恍然大悟的感覺,比如說AQS的原理.如果從目的性的角度來看,那么就是你知道底層原理之后,你的把握性就更強了,如果出了問題,你可以更快的找出來,并且解決.除此之外,你還可以按照具體的現實情況,以源碼為模板在自己造輪子,實現一個更加符合你當前需求的版本.
?后續如果有時間,我希望好好了解一下epoll的操作系統級別的實現原理.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72869.html
摘要:表示的是兩個,當其中任意一個計算完并發編程之是線程安全并且高效的,在并發編程中經常可見它的使用,在開始分析它的高并發實現機制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購,是兩個比較典型的互聯網高并發場景。 干貨:深度剖析分布式搜索引擎設計 分布式,高可用,和機器學習一樣,最近幾年被提及得最多的名詞,聽名字多牛逼,來,我們一步一步來擊破前兩個名詞,今天我們首先來說說分布式。 探究...
摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...
閱讀 2314·2021-11-08 13:13
閱讀 1245·2021-10-09 09:41
閱讀 1683·2021-09-02 15:40
閱讀 3186·2021-08-17 10:13
閱讀 2546·2019-08-29 16:33
閱讀 3122·2019-08-29 13:17
閱讀 3131·2019-08-29 11:00
閱讀 3295·2019-08-26 13:40