日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java NIO类库Selector机制解析--转

發布時間:2025/4/5 java 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java NIO类库Selector机制解析--转 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、??前言

?

自從J2SE 1.4版本以來,JDK發布了全新的I/O類庫,簡稱NIO,其不但引入了全新的高效的I/O機制,同時,也引入了多路復用的異步模式。NIO的包中主要包含了這樣幾種抽象數據類型:

?

  • Buffer:包含數據且用于讀寫的線形表結構。其中還提供了一個特殊類用于內存映射文件的I/O操作。
  • Charset:它提供Unicode字符串影射到字節序列以及逆映射的操作。
  • Channels:包含socketfilepipe三種管道,都是全雙工的通道。
  • Selector:多個異步I/O操作集中到一個或多個線程中(可以被看成是Unixselect()函數的面向對象版本)。

?

我的大學同學趙錕在使用NIO類庫書寫相關網絡程序的時候,發現了一些Java異常RuntimeException,異常的報錯信息讓他開始了對NIOSelector進行了一些調查。當趙錕對我共享了Selector的一些底層機制的猜想和調查時候,我們覺得這是一件很有意思的事情,于是在伙同趙錕進行過一系列的調查后,我倆發現了很多有趣的事情,于是導致了這篇文章的產生。這也是為什么本文的作者署名為我們兩人的原因。

?

先要說明的一點是,趙錕和我本質上都是出身于Unix/Linux/C/C++的開發人員,對于Java,這并不是我們的長處,這篇文章本質上出于對JavaSelector的好奇,因為從表面上來看Selector似乎做到了一些讓我們這些C/C++出身的人比較驚奇的事情。

?

下面讓我來為你講述一下這段故事。

?

二、??故事開始?:?讓C++程序員寫Java程序!

?

沒有嚴重內存問題,大量豐富的SDK類庫,超容易的跨平臺,除了在性能上有些微辭,C++出身的程序員從來都不會覺得Java是一件很困難的事情。當然,對于長期習慣于使用操作系統API(系統調用System Call)的C/C++程序來說,面對Java中的比較“另類”地操作系統資源的方法可能會略感困惑,但萬變不離其宗,只需要對面向對象的設計模式有一定的了解,用不了多長時間,JavaSDK類庫也能玩得隨心所欲。

?

在使用Java進行相關網絡程序的的設計時,出身C/C++的人,首先想到的框架就是多路復用,想到多路復用,Unix/Linux下馬上就能讓從想到select, poll, epoll系統調用。于是,在看到JavaNIO中的Selector類時必然會倍感親切。稍加查閱一下SDK手冊以及相關例程,不一會兒,一個多路復用的框架便呈現出來,隨手做個單元測試,沒啥問題,一切和C/C++照舊。然后告訴兄弟們,框架搞定,以后咱們就在Windows上開發及單元測試,完成后到運行環境Unix上集成測試。心中并暗自念到,跨平臺就好啊,開發活動都可以跨平臺了。

?

然而,好景不長,隨著代碼越來越多,邏輯越來越復雜。好好的框架居然在Windows上單元測試運行開始出現異常,看著Java運行異常出錯的函數棧,異常居然由Selector.open()拋出,錯誤信息居然是Unable to establish loopback connection

?

“Selector.open()居然報loopback connection錯誤,憑什么?不應該啊?open的時候又沒有什么loopbacksocket連接,怎么會報這個錯?

?

長期使用C/C++的程序當然會對操作系統的調用非常熟悉,雖然Java的虛擬機搞的什么系統調用都不見了,但C/C++的程序員必然要比Java程序敏感許多。

?

三、??開始調查?:?怎么Java這么“傻”!

?

于是,C/C++的老鳥從SystemInternals上下載Process Explorer來查看一下究竟是什么個Loopback Connection?果然,打開java運行進程,發現有一些自己連接自己的localhostTCP/IP鏈接。于是另一個問題又出現了,

?

憑什么啊?為什么會有自己和自己的連接?我程序里沒有自己連接自己啊,怎么可能會有這樣的鏈接啊?而自己連接自己的端口號居然是些奇怪的端口。

?

問題變得越來越蹊蹺了。難道這都是Selector.open()在做怪?難道Selector.open()要創建一個自己連接自己的鏈接?寫個程序看看:

?

import?java.nio.channels.Selector;

import?java.lang.RuntimeException;

import?java.lang.Thread;

public?class?TestSelector {

????private?static?final?int?MAXSIZE=5;

????public?static?final?void?main( String argc[] ) {

????????Selector [] sels =?new?Selector[ MAXSIZE];

?

????????????try{

????????????????for(?int?i =?0?;i< MAXSIZE ;++i ) {

????????????????????sels[i] = Selector.open();

????????????????????//sels[i].close();

????????????????}

????????????????Thread.sleep(30000);

????????????}catch( Exception ex ){

????????????????throw?new?RuntimeException( ex );

????????????}

????}

}

?

這個程序什么也沒有,就是做5Selector.open(),然后休息30秒,以便我使用Process Explorer工具來查看進程。程序編譯沒有問題,運行起來,在Process Explorer中看到下面的對話框:(居然有10個連接,從連接端口我們可以知道,互相連接,?如:第一個連第二個,第二個又連第一個)

?
?

不由得贊嘆我們的Java啊,先不說這是不是一件愚蠢的事。至少可以肯定的是,Java在消耗寶貴的系統資源方面,已經可以趕的上某些蠕蟲病毒了。

?

如果不信,不妨把上面程序中的那個MAXSIZE的值改成65535試試,不一會你就會發現你的程序有這樣的錯誤了:(在我的XP機器上大約運行到2000Selector.open()?左右)

?

Exception in thread "main" java.lang.RuntimeException: java.io.IOException:?Unable to establish loopback connection

????????at Test.main(Test.java:18)

Caused by: java.io.IOException:?Unable to establish loopback connection

????????at sun.nio.ch.PipeImpl$Initializer.run(Unknown Source)

????????at java.security.AccessController.doPrivileged(Native Method)

????????at sun.nio.ch.PipeImpl.<init>(Unknown Source)

????????at sun.nio.ch.SelectorProviderImpl.openPipe(Unknown Source)

????????at java.nio.channels.Pipe.open(Unknown Source)

????????at sun.nio.ch.WindowsSelectorImpl.<init>(Unknown Source)

????????at sun.nio.ch.WindowsSelectorProvider.openSelector(Unknown Source)

????????at java.nio.channels.Selector.open(Unknown Source)

????????at Test.main(Test.java:15)

Caused by: java.net.SocketException:?No buffer space available (maximum connections reached?):?connect

????????at sun.nio.ch.Net.connect(Native Method)

????????at sun.nio.ch.SocketChannelImpl.connect(Unknown Source)

????????at java.nio.channels.SocketChannel.open(Unknown Source)

????????... 9 more

?

?

四、??繼續調查?:?如此跨平臺

?

當然,沒人像我們這么變態寫出那么多的Selector.open(),但這正好可以讓我們來明白Java背著大家在干什么事。上面的那些“愚蠢連接”是在Windows平臺上,如果不出意外,Unix/Linux下應該也差不多吧。

?

于是我們把上面的程序放在Linux下跑了跑。使用netstat?命令,并沒有看到自己和自己的Socket連接。貌似在Linux上使用了和Windows不一樣的機制?!

?

如果在Linux上不建自己和自己的TCP連接的話,那么文件描述符和端口都會被省下來了,是不是也就是說我們調用65535Selector.open()的話,應該不會出現異常了。

?

可惜,在實現運行過程序當中,還是一樣報錯:(大約在400Selector.open()左右,還不如Windows

?

Exception in thread "main" java.lang.RuntimeException: java.io.IOException:?Too many open files

????????at Test1.main(Test1.java:19)

Caused by: java.io.IOException:?Too many open files

????????at sun.nio.ch.IOUtil.initPipe(Native Method)

????????at sun.nio.ch.EPollSelectorImpl.<init>(EPollSelectorImpl.java:49)

????????at sun.nio.ch.EPollSelectorProvider.openSelector(EPollSelectorProvider.java:18)

????????at java.nio.channels.Selector.open(Selector.java:209)

????????at Test1.main(Test1.java:15)

?

我們發現,這個異常錯誤是“Too many open files”,于是我想到了使用lsof命令來查看一下打開的文件。

?

看到了有一些pipe文件,一共5對,10個(當然,管道從來都是成對的)。如下圖所示。

?
?

可見,Selector.open()Linux下不用TCP連接,而是用pipe管道。看來,這個pipe管道也是自己給自己的。所以,我們可以得出下面的結論:

?

1)Windows下,Selector.open()會自己和自己建立兩條TCP鏈接。不但消耗了兩個TCP連接和端口,同時也消耗了文件描述符。

2)Linux下,Selector.open()會自己和自己建兩條管道。同樣消耗了兩個系統的文件描述符。

?

估計,在Windows下,SunJVM之所以選擇TCP連接,而不是Pipe,要么是因為性能的問題,要么是因為資源的問題。可能,Windows下的管道的性能要慢于TCP鏈接,也有可能是Windows下的管道所消耗的資源會比TCP鏈接多。這些實現的細節還有待于更為深層次的挖掘。

?

但我們至少可以了解,原來JavaSelector在不同平臺上的機制。

?

?

五、??迷惑不解?:?為什么要自己消耗資源?

?

?

?

令人不解的是為什么我們的JavaNew I/O要設計成這個樣子?如果說老的I/O不能多路復用,如下圖所示,要開N多的線程去挨個偵聽每一個Channel (文件描述符)?,如果這樣做很費資源,且效率不高的話。那為什么在新的I/O機制依然需要自己連接自己,而且,還是重復連接,消耗雙倍的資源?

?

?

?

通過WEB搜索引擎沒有找到為什么。只看到N多的人在報BUG,但SUN卻沒有任何解釋。

?

?

?

下面一個圖展示了,老的IO和新IO的在網絡編程方面的差別。看起來NIO的確很好很強大。但似乎比起C/C++來說,Java的這種實現會有一些不必要的開銷。

?

?

?

六、??它山之石?:?從Apache的Mina框架了解Selector

?

?

?

上面的調查沒過多長時間,正好同學趙錕的一個同事也在開發網絡程序,這位仁兄使用了ApacheMina框架。當我們把Mina框架的源碼研讀了一下后。發現在Mina中有這么一個機制:

?

?

?

1)Mina框架會創建一個Work對象的線程。

?

2)Work對象的線程的run()方法會從一個隊列中拿出一堆Channel,然后使用Selector.select()方法來偵聽是否有數據可以讀/寫。

?

3)最關鍵的是,在select的時候,如果隊列有新的Channel加入,那么,Selector.select()會被喚醒,然后重新select最新的Channel集合。

?

4)要喚醒select方法,只需要調用Selectorwakeup()方法。

?

?

?

對于熟悉于系統調用的C/C++程序員來說,一個阻塞在select上的線程有以下三種方式可以被喚醒:

?

1)??有數據可讀/寫,或出現異常。

?

2)??阻塞時間到,即time out

?

3)??收到一個non-block的信號。可由killpthread_kill發出。

?

所以,Selector.wakeup()要喚醒阻塞的select,那么也只能通過這三種方法,其中:

?

?

?

1)第二種方法可以排除,因為select一旦阻塞,應無法修改其time out時間。

?

2)而第三種看來只能在Linux上實現,Windows上沒有這種信號通知的機制。

?

?

?

所以,看來只有第一種方法了。再回想到為什么每個Selector.open(),在Windows會建立一對自己和自己的loopbackTCP連接;在Linux上會開一對pipepipeLinux下一般都是成對打開),估計我們能夠猜得出來——那就是如果想要喚醒select,只需要朝著自己的這個loopback連接發點數據過去,于是,就可以喚醒阻塞在select上的線程了。

?

?

?

七、??真相大白?:?可愛的Java你太不容易了

?

?

?

使用Linux下的strace命令,我們可以方便地證明這一點。參看下圖。圖中,請注意下面幾點:

?

1)??26654是主線程,之前我輸出notify the select字符串是為了做一個標記,而不至于迷失在大量的strace log中。

?

2)??26662是偵聽線程,也就是select阻塞的線程。

?

3)??圖中選中的兩行。26654write正是wakeup()方法的系統調用,而緊接著的就是26662epoll_wait的返回。


?

?

從上圖可見,這和我們之前的猜想正好一樣。可見,JDKSelector自己和自己建的那些TCP連接或是pipe,正是用來實現Selectornotifywakeup的功能的。

?

?

?

這兩個方法完全是來模仿Linux中的的killpthread_kill給阻塞在select上的線程發信號的。但因為發信號這個東西并不是一個跨平臺的標準(pthread_kill這個系統調用也不是所有Unix/Linux都支持的),而pipe是所有的Unix/Linux所支持的,但Windows又不支持,所以,Windows用了TCP連接來實現這個事。

?

?

?

關于Windows,我一直在想,Windows的防火墻的設置是不是會讓Java的類似的程序執行異常呢?呵呵。如果不知道JavaSDK有這樣的機制,誰知道會有多少個程序為此引起的問題度過多少個不眠之夜,尤其是Java程序員。

?

?

?

八、??后記

?

?

?

文章到這里是可以結束了,但關于Java NIOSelector引出來的其它話題還有許多,比如關于GNU?Java編譯器又是如何,它是否會像SunJava解釋器如此做傻事?我在這里先賣一個關子,關于GNUJava編譯器,我會在另外一篇文章中講述,近期發布,敬請期待。

?

?

?

關于本文中所使用的實驗平臺如下:

?

  • ·????????WindowsWindows XP + SP2, Sun J2SE (build 1.7.0-ea-b23)
  • ·????????LinuxUbuntu 7.10 + Linux Kernel 2.6.22-14-generic, J2SE (build 1.6.0_03-b05)

?

?來源:

?

http://blog.csdn.net/haoel/article/details/2224069

http://blog.csdn.net/haoel/article/details/2224055

?

轉載于:https://www.cnblogs.com/davidwang456/p/3831560.html

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的Java NIO类库Selector机制解析--转的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。