Java NIO之Selector

最后介紹一下Selector主卫,選擇器提供選擇執(zhí)行已經(jīng)就緒的任務(wù)的能力墨坚,這使得多元I/O成為了可能殴泰,就緒執(zhí)行和多元選擇使得單線程能夠有效地同時(shí)管理多個(gè)I/O通道隔躲。選擇器的執(zhí)行主要分為以下幾個(gè)步驟:

1芒涡、創(chuàng)建一個(gè)或者多個(gè)可選擇的通道(SelectableChannel)

2柴灯、將這些創(chuàng)建的通道注冊到選擇器對象中

3卖漫、選擇器會記住開發(fā)者關(guān)心的通道,它們也會追蹤對應(yīng)的通道是否已經(jīng)就緒

4弛槐、開發(fā)者調(diào)用一個(gè)選擇器對象的select()方法懊亡,當(dāng)方法從阻塞狀態(tài)返回時(shí),選擇鍵會被更新

5乎串、獲取選擇鍵的集合店枣,找到當(dāng)時(shí)已經(jīng)就緒的通道,通過遍歷這些鍵叹誉,開發(fā)者可以選擇對已就緒的通道要做的操作

選擇器

選擇器的作用是管理了被注冊的通道集合和它們的就緒狀態(tài)鸯两,假設(shè)我們有四個(gè)Socket通道的選擇器,可以通過下面方式創(chuàng)建:

Selector selector = Selector.open(); 
channel1.register(selector, SelectionKey.OP_READ); 
channel2.register(selector, SelectionKey.OP_WRITE); 
channel3.register(selector, SelectionKey.OP_READ | OP_WRITE); 
channel4.register(selector, SelectionKey.OP_READ | OP_ACCEPT); 
ready = selector.select(10000); 

select()方法在將線程置于睡眠狀態(tài)直到這些感興趣的事件中的一個(gè)發(fā)生或者10秒鐘過去长豁,這就是所謂的事件驅(qū)動(dòng)钧唐。


通道是調(diào)用register方法注冊到選擇器上的,從代碼里面可以看到register()方法接受一個(gè)Selector對象作為參數(shù)匠襟,以及一個(gè)名為ops的整數(shù)型參數(shù)钝侠,第二個(gè)參數(shù)表示關(guān)心的通道操作。有四種被定義的可選擇操作:讀(read)酸舍、寫(write)帅韧、連接(connect)和接受(accept)。

注意并非所有的操作都在所有的可選擇通道上被支持啃勉,例如SocketChannel就不支持accept忽舟。

選擇鍵

一個(gè)鍵表示一個(gè)特定的通道對象和一個(gè)特定的選擇器對象之間的注冊關(guān)系。

public abstract class SelectionKey
{
    public static final int OP_READ;
    public static final int OP_WRITE;
    public static final int OP_CONNECT;
    public static final int OP_ACCEPT;
    public abstract SelectableChannel channel();
    public abstract Selector selector();
    public abstract void cancel();
    public abstract boolean isValid();
    public abstract int interestOps();
    public abstract void iterestOps(int ops);
    public abstract int readyOps();
    public final boolean isReadable();
    public final boolean isWritable();
    public final boolean isConnectable();
    public final boolean isAcceptable();
    public final Object attach(Object ob);
    public final Object attachment();
}

選擇器維護(hù)著注冊過的通道的集合淮阐,并且這些注冊關(guān)系中的任意一個(gè)都是封裝在SelectionKey對象中的叮阅。每一個(gè)Selector對象維護(hù)三種鍵的集合:

public abstract class Selector
{
    ...
    public abstract Set keys();
    public abstract Set selectedKeys();
    public abstract int select() throws IOException;
    public abstract int select(long timeout) throws IOException;
    public abstract int selectNow() throws IOException;
    public abstract void wakeup();
    ...   
}

已注冊的鍵的集合(Registered key set)
與選擇器關(guān)聯(lián)的已經(jīng)注冊的鍵的集合,并不是所有注冊過的鍵都有效泣特,這個(gè)集合通過keys()方法返回浩姥,并且可能是空的。這些鍵的集合是不可以直接修改的状您,試圖這么做將引發(fā)java.lang.UnsupportedOperationException及刻。

已選擇的鍵的集合(Selected key set)
已注冊的鍵的集合的子集,這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器判斷為已經(jīng)準(zhǔn)備好的并且包含于鍵的interest集合中的操作竞阐。這個(gè)集合通過selectedKeys()方法返回(有可能是空的)缴饭。鍵可以直接從這個(gè)集合中移除,但不能添加骆莹。試圖向已選擇的鍵的集合中添加元素將拋java.lang.UnsupportedOperationException颗搂。

已取消的鍵的集合(Cancelled key set)
已注冊的鍵的集合的子集,這個(gè)集合包含了cancel()方法被調(diào)用過的鍵(這個(gè)鍵已經(jīng)被無效化)幕垦,但它們還沒有被注銷丢氢。這個(gè)集合是選擇器對象的私有成員傅联,因而無法直接訪問。

下面結(jié)合之前的Channel和Buffer疚察,看一下如何寫和使用選擇器實(shí)現(xiàn)服務(wù)端Socket數(shù)據(jù)接收的程序蒸走。

服務(wù)端

 1 public class SelectorServer
 2 {
 3     private static int PORT = 1234;
 4     
 5     public static void main(String[] args) throws Exception
 6     {
 7         // 先確定端口號
 8         int port = PORT;
 9         if (args != null && args.length > 0)
10         {
11             port = Integer.parseInt(args[0]);
12         }
13         // 打開一個(gè)ServerSocketChannel
14         ServerSocketChannel ssc = ServerSocketChannel.open();
15         // 獲取ServerSocketChannel綁定的Socket
16         ServerSocket ss = ssc.socket();
17         // 設(shè)置ServerSocket監(jiān)聽的端口
18         ss.bind(new InetSocketAddress(port));
19         // 設(shè)置ServerSocketChannel為非阻塞模式
20         ssc.configureBlocking(false);
21         // 打開一個(gè)選擇器
22         Selector selector = Selector.open();
23         // 將ServerSocketChannel注冊到選擇器上去并監(jiān)聽accept事件
24         ssc.register(selector, SelectionKey.OP_ACCEPT);
25         while (true)
26         {
27             // 這里會發(fā)生阻塞,等待就緒的通道
28             int n = selector.select();
29             // 沒有就緒的通道則什么也不做
30             if (n == 0)
31             {
32                 continue;
33             }
34             // 獲取SelectionKeys上已經(jīng)就緒的通道的集合
35             Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
36             // 遍歷每一個(gè)Key
37             while (iterator.hasNext())
38             {
39                 SelectionKey sk = iterator.next();
40                 // 通道上是否有可接受的連接
41                 if (sk.isAcceptable())
42                 {
43                     ServerSocketChannel ssc1 = (ServerSocketChannel)sk.channel();
44                     SocketChannel sc = ssc1.accept();
45                     sc.configureBlocking(false);
46                     sc.register(selector, SelectionKey.OP_READ);
47                 }
48                 // 通道上是否有數(shù)據(jù)可讀
49                 else if (sk.isReadable())
50                 {
51                     readDataFromSocket(sk);
52                 }
53                 iterator.remove();
54             }
55         }
56     }
57     
58     private static ByteBuffer bb = ByteBuffer.allocate(1024);
59     
60     // 從通道中讀取數(shù)據(jù)
61     protected static void readDataFromSocket(SelectionKey sk) throws Exception
62     {
63         SocketChannel sc = (SocketChannel)sk.channel();
64         bb.clear();
65         while (sc.read(bb) > 0)
66         {
67             bb.flip();
68             while (bb.hasRemaining())
69             {
70                 System.out.print((char)bb.get());
71             }
72             System.out.println();
73             bb.clear();
74         }
75     }
76 }

滿足isAcceptable()則表示該通道上有數(shù)據(jù)到來了貌嫡,此時(shí)我們做的事情不是獲取該通道—>創(chuàng)建一個(gè)線程來讀取該通道上的數(shù)據(jù)比驻,這么做就和BIO沒有區(qū)別了。我們做的事情只是簡單地將對應(yīng)的SocketChannel注冊到選擇器上岛抄,通過傳入OP_READ標(biāo)記别惦,告訴選擇器我們關(guān)心新的Socket通道什么時(shí)候可以準(zhǔn)備好讀數(shù)據(jù)。

滿足isReadable()則表示新注冊的Socket通道已經(jīng)可以讀取數(shù)據(jù)了夫椭,此時(shí)調(diào)用readDataFromSocket方法讀取SocketChannel中的數(shù)據(jù)掸掸。

客戶端

選擇器客戶端的代碼,沒什么要求蹭秋,只要向服務(wù)器端發(fā)送數(shù)據(jù)就可以了扰付。

1 public class SelectorClient
 2 {
 3     private static final String STR = "Hello World!";
 4     private static final String REMOTE_IP = "127.0.0.1";
 5     private static final int THREAD_COUNT = 5;
 6     
 7     private static class NonBlockingSocketThread extends Thread
 8     {
 9         public void run()
10         {
11             try
12             {
13                 int port = 1234;
14                 SocketChannel sc = SocketChannel.open();
15                 sc.configureBlocking(false);
16                 sc.connect(new InetSocketAddress(REMOTE_IP, port));
17                 while (!sc.finishConnect())
18                 {
19                     System.out.println("同" + REMOTE_IP + "的連接正在建立,請稍等仁讨!");
20                     Thread.sleep(10);
21                 }
22                 System.out.println("連接已建立悯周,待寫入內(nèi)容至指定ip+端口!時(shí)間為" + System.currentTimeMillis());
23                 String writeStr = STR + this.getName();
24                 ByteBuffer bb = ByteBuffer.allocate(writeStr.length());
25                 bb.put(writeStr.getBytes());
26                 bb.flip(); // 寫緩沖區(qū)的數(shù)據(jù)之前一定要先反轉(zhuǎn)(flip)
27                 sc.write(bb);
28                 bb.clear();
29                 sc.close();
30             } 
31             catch (IOException e)
32             {
33                 e.printStackTrace();
34             } 
35             catch (InterruptedException e)
36             {
37                 e.printStackTrace();
38             }
39         }
40     }
41     
42     public static void main(String[] args) throws Exception
43     {
44         NonBlockingSocketThread[] nbsts = new NonBlockingSocketThread[THREAD_COUNT];
45         for (int i = 0; i < THREAD_COUNT; i++)
46             nbsts[i] = new NonBlockingSocketThread();
47         for (int i = 0; i < THREAD_COUNT; i++)
48             nbsts[i].start();
49         // 一定要join保證線程代碼先于sc.close()運(yùn)行陪竿,否則會有AsynchronousCloseException
50         for (int i = 0; i < THREAD_COUNT; i++)
51             nbsts[i].join();
52     }
53 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眯娱,一起剝皮案震驚了整個(gè)濱河市扣墩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌举娩,老刑警劉巖锐墙,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礁哄,死亡現(xiàn)場離奇詭異,居然都是意外死亡溪北,警方通過查閱死者的電腦和手機(jī)桐绒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來之拨,“玉大人茉继,你說我怎么就攤上這事∈辞牵” “怎么了烁竭?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吉挣。 經(jīng)常有香客問我派撕,道長婉弹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任终吼,我火速辦了婚禮镀赌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘际跪。我一直安慰自己商佛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布垫卤。 她就那樣靜靜地躺著威彰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穴肘。 梳的紋絲不亂的頭發(fā)上歇盼,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音评抚,去河邊找鬼豹缀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慨代,可吹牛的內(nèi)容都是我干的邢笙。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼侍匙,長吁一口氣:“原來是場噩夢啊……” “哼氮惯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起想暗,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤妇汗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后说莫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杨箭,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年储狭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了互婿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辽狈,死狀恐怖慈参,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刮萌,我是刑警寧澤懂牧,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響僧凤,放射性物質(zhì)發(fā)生泄漏畜侦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一躯保、第九天 我趴在偏房一處隱蔽的房頂上張望旋膳。 院中可真熱鬧,春花似錦途事、人聲如沸验懊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽义图。三九已至,卻和暖如春召烂,著一層夾襖步出監(jiān)牢的瞬間碱工,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工奏夫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怕篷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓酗昼,卻偏偏與公主長得像廊谓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子麻削,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 1蒸痹、選擇器基礎(chǔ) 1.1、選擇器呛哟、可選擇通道叠荠、選擇鍵類 選擇器(Selector): 選擇器類管理著一個(gè)被注冊的通道...
    橋頭放牛娃閱讀 1,137評論 0 6
  • Android網(wǎng)絡(luò)編程 目錄 1、Java NIO 介紹 NIO是java New IO的簡稱竖共,在jdk1.4里提...
    香沙小熊閱讀 4,712評論 0 6
  • 在本章中,我們將探索選擇器(selectors)俺祠。選擇器提供選擇執(zhí)行已經(jīng)就緒的任務(wù)的能力公给,這使得多元 I/O 成為...
    沉淪2014閱讀 649評論 0 2
  • 貧瘠的非洲草原上 大象從出生 走到死亡的盡頭 一代又一代 只有土地在延續(xù) 不斷孕育新的生命 不斷埋藏新的生命
    青木西閱讀 206評論 0 0
  • 給所有點(diǎn)加標(biāo)簽text() 求相關(guān)系數(shù) 多個(gè)變量逐對比較的散點(diǎn)圖 這里是選擇出屬性AB>100的運(yùn)動(dòng)員,然后兩兩比...
    Lefaith閱讀 2,338評論 0 0