JavaNIO類庫Selector機制解析_第1頁
JavaNIO類庫Selector機制解析_第2頁
JavaNIO類庫Selector機制解析_第3頁
JavaNIO類庫Selector機制解析_第4頁
JavaNIO類庫Selector機制解析_第5頁
已閱讀5頁,還剩8頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1、JavaNIO類庫Selector機制解析、八、前言自從J2SE1.4版本以來,JDK發(fā)布了全新的I/O類庫,簡稱NIO,其不但引入了全新的高效的I/O機制,同時,也引入了多路復(fù)用的異步模式。NIO的包中主要包含了這樣幾種抽象數(shù)據(jù)類型:Buffer:包含數(shù)據(jù)且用于讀寫的線形表結(jié)構(gòu)。其中還提供了一個特殊類用于內(nèi)存映射文件的I/O操作。Charset:它提供Unicode字符串影射到字節(jié)序列以及逆映射的操作。Channels:包含socket,file和pipe三種管道,都是全雙工的通道。Selector:多個異步I/O操作集中到一個或多個線程中(可以被看成是Unix中select()函數(shù)的面向?qū)?/p>

2、象版本)。我的大學(xué)同學(xué)趙錕在使用NIO類庫書寫相關(guān)網(wǎng)絡(luò)程序的時候,發(fā)現(xiàn)了一些Java異常RuntimeException,異常的報錯信息讓他開始了對NIO的Selector進行了一些調(diào)查。當(dāng)趙錕對我共享了Selector的一些底層機制的猜想和調(diào)查時候,我們覺得這是一件很有意思的事情,于是在伙同趙錕進行過一系列的調(diào)查后,我倆發(fā)現(xiàn)了很多有趣的事情,于是導(dǎo)致了這篇文章的產(chǎn)生。這也是為什么本文的作者署名為我們兩人的原因。先要說明的一點是,趙錕和我本質(zhì)上都是出身于Unix/Linux/C/C+的開發(fā)人員,對于Java,這并不是我們的長處,這篇文章本質(zhì)上出于對Java的Selector的好奇,因為從表面上

3、來看Selector似乎做到了一些讓我們這些C/C+出身的人比較驚奇的事情。面讓我來為你講述一下這段故事。、故事開始:讓C+程序員寫Java程序!沒有嚴(yán)重內(nèi)存問題,大量豐富的SDK類庫,超容易的跨平臺,除了在性能上有些微辭,C+出身的程序員從來都不會覺得Java是一件很困難的事情。當(dāng)然,對于長期習(xí)慣于使用操作系統(tǒng)API(系統(tǒng)調(diào)用SystemCall)的C/C+程序來說,面對Java中的比較“另類”地操作系統(tǒng)資源的方法可能會略感困惑,但萬變不離其宗,只需要對面向?qū)ο蟮脑O(shè)計模式有一定的了解,用不了多長時間,Java的SDK類庫也能玩得隨心所欲。在使用Java進行相關(guān)網(wǎng)絡(luò)程序的的設(shè)計時,出身C/C+

4、的人,首先想到的框架就是多路復(fù)用,想到多路復(fù)用,Unix/Linux下馬上就能讓從想到select,poll,epoll系統(tǒng)調(diào)用。于是,在看到Java的NIO中的Selector類時必然會倍感親切。稍加查閱一下SDK手冊以及相關(guān)例程,不一會兒,一個多路復(fù)用的框架便呈現(xiàn)出來,隨手做個單元測試,沒啥問題,一切和C/C+照舊。然后告訴兄弟們,框架搞定,以后咱們就在Windows上開發(fā)及單元測試,完成后到運行環(huán)境Unix上集成測試。心中并暗自念到,跨平臺就好啊,開發(fā)活動都可以跨平臺了。然而,好景不長,隨著代碼越來越多,邏輯越來越復(fù)雜。好好的框架居然在Windows上單元測試運行開始出現(xiàn)異常,看著Jav

5、a運行異常出錯的函數(shù)棧,異常居然由Selector.open()拋出,錯誤信息居然是Unabletoestablishloopbackconnection?!癝elector.open()居然報loopbackconnection錯誤,憑什么?不應(yīng)該啊?open的時候又沒有什么loopback的socket連接,怎么會報這個錯?”長期使用C/C+的程序當(dāng)然會對操作系統(tǒng)的調(diào)用非常熟悉,雖然Java的虛擬機搞的什么系統(tǒng)調(diào)用都不見了,但C/C+的程序員必然要比Java程序敏感許多。三、開始調(diào)查:怎么Java這么“傻”!于是,C/C+的老鳥從Systemlnternals上下載ProcessExplo

6、rer來查看一下究竟是什么個LoopbackConnectiono果然,打開java運行進程,發(fā)現(xiàn)有一些自己連接自己的localhost的TCP/IP鏈接。于是另一個問題又出現(xiàn)了,“憑什么?。繛槭裁磿凶约汉妥约旱倪B接?我程序里沒有自己連接自己啊,怎么可能會有這樣的鏈接???而自己連接自己的端口號居然是些奇怪的端口?!眴栴}變得越來越蹊蹺了。難道這都是Selector.open()在做怪?難道Selector.open()要創(chuàng)建一個自己連接自己的鏈接?寫個程序看看:importjava.nio.channels.Selector;importjava.lang.RuntimeException;i

7、mportjava.lang.Thread;publicclassTestSelectorprivatestaticfinalintMAXSIZE=5;publicstaticfinalvoidmain(Stringargc)Selectorsels=newSelectorMAXSIZE;tryfor(inti=0;iMAXSIZE;+i)selsi=Selector.open();/selsi.close();Thread.sleep(30000);catch(Exceptionex)thrownewRuntimeException(ex);這個程序什么也沒有,就是做5次Selector.o

8、pen(),然后休息30秒,以便我使用ProcessExplorer工具來查看進程。程序編譯沒有問題,運行起來,在ProcessExplorer中看到下面的對話框:(居然有10個連接,從連接端口我們可以知道,互相連接,如:第一個連第二個,第二個又連第一個)不由得贊嘆我們的Java啊,先不說這是不是一件愚蠢的事。至少可以肯定的是,Java在消耗寶貴的系統(tǒng)資源方面,已經(jīng)可以趕的上某些蠕蟲病毒了。如果不信,不妨把上面程序中的那個MAXSIZE的值改成65535試試,不一會你就會發(fā)現(xiàn)你的程序有這樣的錯誤了:(在我的XP機器上大約運行到2000個Selector.open()左右)Exceptionin

9、threadmainjava.lang.RuntimeException:java.io.IOException:UnabletoestablishloopbackconnectionatTest.main(Test.java:18)Causedby:java.io.IOException:Unabletoestablishloopbackconnectionatsun.nio.ch.PipeImpl$Initializer.run(UnknownSource)atjava.security.AccessController.doPrivileged(NativeMethod)atsun.ni

10、o.ch.PipeImpl.(UnknownSource)atsun.nio.ch.SelectorProviderImpl.openPipe(UnknownSource)atjava.nio.channels.Pipe.open(UnknownSource)atsun.nio.ch.WindowsSelectorImpl.(UnknownSource)atsun.nio.ch.WindowsSelectorProvider.openSelector(UnknownSource)atjava.nio.channels.Selector.open(UnknownSource)atTest.mai

11、n(Test.java:15)Causedby:.SocketException:Nobufferspaceavailable(maximumconnectionsreached?):connectatsun.nio.ch.Net.connect(NativeMethod)atsun.nio.ch.SocketChannelImpl.connect(UnknownSource)atjava.nio.channels.SocketChannel.open(UnknownSource).9more四、繼續(xù)調(diào)查:如此跨平臺當(dāng)然,沒人像我們這么變態(tài)寫出那么多的Selector.open(),但這正好可

12、以讓我們來明白Java背著大家在干什么事。上面的那些“愚蠢連接”是在Windows平臺上,如果不出意外,Unix/Linux下應(yīng)該也差不多吧。于是我們把上面的程序放在Linux下跑了跑。使用netstat命令,并沒有看到自己和自己的Socket連接。貌似在Linux上使用了和Windows不一樣的機制?!如果在Linux上不建自己和自己的TCP連接的話,那么文件描述符和端口都會被省下來了,是不是也就是說我們調(diào)用65535個Selector.open()的話,應(yīng)該不會出現(xiàn)異常了??上?,在實現(xiàn)運行過程序當(dāng)中,還是一樣報錯:(大約在400個Selector.open()左右,還不如Windows)E

13、xceptioninthreadmainjava.lang.RuntimeException:java.io.IOException:ToomanyopenfilesatTest1.main(Test1.java:19)Causedby:java.io.IOException:Toomanyopenfilesatsun.nio.ch.IOUtil.initPipe(NativeMethod)atsun.nio.ch.EPollSelectorImpl.(EPollSelectorImpl.java:49)atsun.nio.ch.EPollSelectorProvider.openSelect

14、or(EPollSelectorProvider.java:18)atjava.nio.channels.Selector.open(Selector.java:209)atTest1.main(Test1.java:15)我們發(fā)現(xiàn),這個異常錯誤是“Toomanyopenfiles”,于是我想到了使用lsof命令來查看一下打開的文件??吹搅擞幸恍﹑ipe文件,一共5對,10個(當(dāng)然,管道從來都是成對的)。如下圖所示。文件駕輯W繆鞘hdi3nCHiitYtu!hdEnfltfeuitU叨盹/Iib/1d-2.S.l.eo/dev/pts/0Zdev/pts/O/dev/pts/O/nsr/lib

15、打糊打凱牆-&s-uft-l.G.0.O(3/jA學(xué)tb:mrra摘論ZjaiT-VIJJjaw25578hchenjavg2557Slichejavai2578hchen.jaira25E78LclieiiJava.2&578掄出壯t,$arjava_25573hchenjawsi2578hcheti伽電hhen.Java25578hchenjaira.25E78licheejawa25578hchen.255TShchetljava2&57Sbchenjava.2557Shchenjava2557Shchen.Java2557ShehsnJava.25578hchen.jmvs25578h

16、chesijava25&73hcJ-enjava25刃呂hchen.hchenJubtrntLi;VJ$Lch&nJ&ubuirtii:缶mcnl2u3r010914S997058222S50210oon-ooooooorT-vrr-Tr-OTJTJTJOT-TCTx1rxTx亠TTxTvh-.J.VTx_EETA*FFOFFOFFOFFOFFO6676G766TG67667oooooooonvoooooo000-0032621326217249262292G227249E6Z3S262373492G2492624734S63724pipeUipe/aiKn_iDad?:jleventpol1

17、Pipepips/anninode-:Intpol11pupeplr巳AtiFi:evenxpol1pip。Pipe/anofLinode:eirtpal1JPiflepipe/anfin_iiKdeiento!j可見,Selector.open()在Linux下不用TCP連接,而是用pipe管道??磥?,這個pipe管道也是自己給自己的。所以,我們可以得出下面的結(jié)論:Windows下,Selector.open()會自己和自己建立兩條TCP鏈接。不但消耗了兩個TCP連接和端口,同時也消耗了文件描述符。Linux下,Selector.open。會自己和自己建兩條管道。同樣消耗了兩個系統(tǒng)的文件描述

18、符。估計,在Windows下,Sun的JVM之所以選擇TCP連接,而不是Pipe,要么是因為性能的問題,要么是因為資源的問題??赡埽琖indows下的管道的性能要慢于TCP鏈接,也有可能是Windows下的管道所消耗的資源會比TCP鏈接多。這些實現(xiàn)的細(xì)節(jié)還有待于更為深層次的挖掘。但我們至少可以了解,原來Java的Selector在不同平臺上的機制。五、迷惑不解:為什么要自己消耗資源?令人不解的是為什么我們的Java的NewI/O要設(shè)計成這個樣子?如果說老的I/O不能多路復(fù)用,如下圖所示,要開N多的線程去挨個偵聽每一個Channel(文件描述符),如果這樣做很費資源,且效率不高的話。那為什么在新

19、的I/O機制依然需要自己連接自己,而且,還是重復(fù)連接,消耗雙倍的資源?通過WEB搜索引擎沒有找到為什么。只看到N多的人在報BUG,但SUN卻沒有任何解釋。下面一個圖展示了,老的IO和新IO的在網(wǎng)絡(luò)編程方面的差別??雌饋鞱IO的確很好很強大。但似乎比起C/C+來說,Java的這種實現(xiàn)會有一些不必要的開銷。SockrtChann日Ireist-srregale-ThreadOldIOSmanythreadsLiook白dcmpeadnngEpoinnmiLtijlesocketsSwK1-SacksKJacketSakatInputSfreiLmInputstrea-Ti1npulStBanSot

20、kelChdnnelScckBEChann已setect1IL111BireadThreadThreadThraarirsa.dSockedSocketSockSocket*珂ewIO,singletlueadaockeiloiLselection六、它山之石:從Apache的Mina框架了解Selector上面的調(diào)查沒過多長時間,正好同學(xué)趙錕的一個同事也在開發(fā)網(wǎng)絡(luò)程序,這位仁兄使用了Apache的Mina框架。當(dāng)我們把Mina框架的源碼研讀了一下后。發(fā)現(xiàn)在Mina中有這么一個機制:Mina框架會創(chuàng)建一個Work對象的線程。Work對象的線程的run()方法會從一個隊列中拿出一堆Channel

21、,然后使用Selector.select()方法來偵聽是否有數(shù)據(jù)可以讀/寫。最關(guān)鍵的是,在select的時候,如果隊列有新的Channel加入,那么,Selectorselect()會被喚醒,然后重新select最新的Channel集合。要喚醒select方法,只需要調(diào)用Selector的wakeup()方法。對于熟悉于系統(tǒng)調(diào)用的C/C+程序員來說,一個阻塞在select上的線程有以下三種方式可以被喚醒:有數(shù)據(jù)可讀/寫,或出現(xiàn)異常。阻塞時間到,即timeout。收到一個non-block的信號??捎蒶ill或pthread_kill發(fā)出。所以,Selector.wakeup()要喚醒阻塞的se

22、lect,那么也只能通過這三種方法,其中:第二種方法可以排除,因為select一旦阻塞,應(yīng)無法修改其timeout時間。而第三種看來只能在Linux上實現(xiàn),Windows上沒有這種信號通知的機制。所以,看來只有第一種方法了。再回想到為什么每個Selector.open(),在Windows會建立一對自己和自己的loopback的TCP連接;在Linux上會開一對pipe(pipe在Linux下一般都是成對打開),估計我們能夠猜得出來那就是如果想要喚醒select,只需要朝著自己的這個loopback連接發(fā)點數(shù)據(jù)過去,于是,就可以喚醒阻塞在select上的線程了。七、真相大白:可愛的Java你太

23、不容易了使用Linux下的strace命令,我們可以方便地證明這一點。參看下圖。圖中,請注意下面幾點:26654是主線程,之前我輸出notifytheselect字符串是為了做一個標(biāo)記,而不至于迷失在大量的stracelog中。26662是偵聽線程,也就是select阻塞的線程。圖中選中的兩行。26654的write正是wakeup()方法的系統(tǒng)調(diào)用,而緊接著的就是26662的epoll_wait的返回。文件輪挹查春續(xù)端標(biāo)簽蒂勵Of/jayahdhanJOLixnytLi;*j萍再2S6S1get.timeofcly(120656788,S631SO,EJULLJ=0:茨拠1gettinieo

24、fday(1206567S61,呂63792hNULL)二0.26651cloci.gettimeCCLra.RELIIWE,120656TB64,0643T05S6J-)=0266S1futex25G54,futesre&uoie)-1HfHEDOUT(ConnecticntinedoutTOC o 1-5 h z26S54futex0k80S98dO?FUTB_WAKE?1)=0266&4cl(CLOCLMDTONIC,3S732r773994559)=026654writed,notifytheseleet?F,17)土1736654write(5RW1unfinisfiedg6E5EJ

25、、卸oH_wait劭nmedEFDLUtb(諂羽已u6辰侶082T795525413352適h1伍乂-心三12fi654vr譏g1)=1255&4i.writeresumed=1:26E523et-timeo-fday(tmfijiishiel2G654TunapSTdaiOOO,12288.PROT.REtD|PROfT.WRIIT|PROT.EJCEC.W_PRIVATE|JIAP.FIJO|JIAP_AUDM|7MDIEJWNQEESEBVE,-1,02662.gattine&f-dajjresusie(12065GT7ES4,3S757E),HULL)=0266M、-0i1da4000

26、.266S2resiiJ(S.266&4rt_sigpKJcrk(JSG.SEDMSKpL26562飛T爲(wèi)128)=126654J、.rt_sjgprocmaftrssmiEcI-NULLt=0Iit-12G6G2vrite(l?dselectreturn,ISunfiiii51Kd從上圖可見,這和我們之前的猜想正好一樣??梢?,JDK的Selector自己和自己建的那些TCP連接或是pipe,正是用來實現(xiàn)Selector的notify和wakeup的功能的。這兩個方法完全是來模仿Linux中的的kill和pthread_kill給阻塞在select上的線程發(fā)信號的。但因為發(fā)信號這個東西并不是一

27、個跨平臺的標(biāo)準(zhǔn)(pthread_kill這個系統(tǒng)調(diào)用也不是所有Unix/Linux都支持的),而pipe是所有的Unix/Linux所支持的,但Windows又不支持,所以,Windows用了TCP連接來實現(xiàn)這個事。關(guān)于Windows,我一直在想,Windows的防火墻的設(shè)置是不是會讓Java的類似的程序執(zhí)行異常呢?呵呵。如果不知道Java的SDK有這樣的機制,誰知道會有多少個程序為此引起的問題度過多少個不眠之夜,尤其是Java程序員。八、后記文章到這里是可以結(jié)束了,但關(guān)于JavaNIO的Selector引出來的其它話題還有許多,比如關(guān)于GNU的Java編譯器又是如何,它是否會像Sun的Jav

28、a解釋器如此做傻事?我在這里先賣一個關(guān)子,關(guān)于GNU的Java編譯器,我會在另外一篇文章中講述,近期發(fā)布,敬請期待。關(guān)于本文中所使用的實驗平臺如下:Windows:WindowsXP+SP2,SunJ2SE(build1.7.0-ea-b23)Linux:Ubuntu7.10+LinuxKernel2.6.22-14-generic,J2SE(build1.6.0_03-b05)本文主要的調(diào)查工作由我的大學(xué)同學(xué)趙錕完成,我?guī)推潋炞C調(diào)查成果及猜想。在此也向大家介紹我的大學(xué)同學(xué)趙錕,他也是一個技術(shù)高手,在軟件開發(fā)方面,特別是Unix/LinuxC/C+方面有著相當(dāng)?shù)墓Φ?,相信自此以后,會有很多文?/p>

29、會由我和他一同發(fā)布。JavaNIO類庫Selector機制解析(續(xù))在前些天的JavaNIO類庫Selector機制解析文章中,我們知道了下面的事情:Sun的JVM在實現(xiàn)Selector上,在Linux和Windows平臺下的細(xì)節(jié)。Selector類的wakeup()方法如何喚醒阻塞在select。系統(tǒng)調(diào)用上的細(xì)節(jié)。先給大家做一個簡單的回顧,在Windows下,Sun的Java虛擬機在Selector.open()時會自己和自己建立loopback的TCP鏈接;在Linux下,Selector會創(chuàng)建pipe。這主要是為了Selector.wakeup()可以方便喚醒阻塞在select()系統(tǒng)調(diào)

30、用上的線程(通過向自己所建立的TCP鏈接和管道上隨便寫點什么就可以喚醒阻塞線程)我們知道,無論是建立TCP鏈接還是建立管道都會消耗系統(tǒng)資源,而在Windows上,某些Windows上的防火墻設(shè)置還可能會導(dǎo)致Java的Selector因為建立不起loopback的TCP鏈接而出現(xiàn)異常。而在我的另一篇文章用GDB調(diào)試Java程序中介紹了另一個Java的解釋器GNU的gj以及編譯器gcj,不但可以比較高效地運行Java程序,而且還可以把Java程序直接編譯成可執(zhí)行文件。GNU的之所以要重做一個Java的編譯和解釋器,其一個重要原因就是想解釋Sun的JVM的效率和資源耗費問題。當(dāng)然,GNU的Java編

31、譯/解釋器并不需要考慮太多復(fù)雜的平臺,他們只需要專注于Linux和衍生自UnixSystemV的操作系統(tǒng),對于開發(fā)人員來說,離開了Windows,一切都會變得簡單起來。在這里,讓我們看看GNU的gij是如何解釋Selector.open()和Selector.wakeup()的。同樣,我們需要一個測試程序。在這里,為了清晰,我不會例出所有的代碼,我只給出我所使用的這個程序的一些關(guān)鍵代碼。我的這個測試程序中,和所有的Socket程序一樣,下面是一個比較標(biāo)準(zhǔn)的框架,當(dāng)然,這個框架應(yīng)該是在一個線程中,也就是一個需要繼承Runnable接口,并實現(xiàn)run()方法的一個類。(注意:其中的s是一個成員變量

32、,是Selector類型,以便主線程序使用)/生成一個偵聽端ServerSocketChannelssc=ServerSocketChannel.open();/將偵聽端設(shè)為異步方式ssc.configureBlocking(false);/生成一個信號監(jiān)視器s=Selector.open();/偵聽端綁定到一個端口ssc.socket().bind(newInetSocketAddress(port);/設(shè)置偵聽端所選的異步信號OP_ACCEPTssc.register(s,SelectionKey.OP_ACCEPT);System.out.println(echoserverhasbeensetup);while(true)intn=s.select();if(n=0)/沒有指定的I/O事件發(fā)生continue;Iteratorit=s.selectedKeys().iterator();while(it.hasNext()Selectio

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論