小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Merlin 給 Java 平臺(tái)帶來(lái)了非阻塞 I/O

 shaobin0604@163.com 2007-11-29


2002 年 3 月 12 日

Java 技術(shù)平臺(tái)早就應(yīng)該提供非阻塞 I/O 機(jī)制了。幸運(yùn)的是,Merlin(JDK 1.4)有一根幾乎在各個(gè)場(chǎng)合都適用的魔杖,而解除阻塞了的 I/O 的阻塞狀態(tài)正是這位魔術(shù)師的專長(zhǎng)。軟件工程師 Aruna Kalagnanam 和 Balu G 介紹了 Merlin 的新 I/O 包 ― java.nio(NIO)― 的這種非阻塞功能,并且用一個(gè)套接字編程示例向您展示 NIO 能做些什么。請(qǐng)單擊本文頂部或底部的 討論,在 討論論壇與作者及其他讀者分享您關(guān)于本文的心得。

服務(wù)器在合理的時(shí) 間之內(nèi)處理大量客戶機(jī)請(qǐng)求的能力取決于服務(wù)器使用 I/O 流的效率。同時(shí)為成百上千個(gè)客戶機(jī)提供服務(wù)的服務(wù)器必須能夠并發(fā)地使用 I/O 服務(wù)。Java 平臺(tái)直到 JDK 1.4(也就是 Merlin)才支持非阻塞 I/O 調(diào)用。用 Java 語(yǔ)言寫的服務(wù)器,由于其線程與客戶機(jī)之比幾乎是一比一,因而易于受到大量線程開銷的影響,其結(jié)果是既導(dǎo)致了性能問(wèn)題又缺乏可伸縮性。

為了解決這個(gè)問(wèn)題,Java 平臺(tái)的最新發(fā)行版引入了一組新的類。Merlin 的 java.nio 包充滿了解決線程開銷問(wèn)題的技巧,包中最重要的是新的 SelectableChannel 類和 Selector 類。 通道(channel)是客戶機(jī)和服務(wù)器之間的一種通信方式。 選擇器(selector)與 Windows 消息循環(huán)類似,它從不同客戶機(jī)捕獲各種事件并將它們分派到相應(yīng)的事件處理程序。在本文,我們將向您展示這兩個(gè)類如何協(xié)同工作,從而為 Java 平臺(tái)創(chuàng)建非阻塞 I/O 機(jī)制。

Merlin 之前的 I/O 編程

我們將從考察基礎(chǔ)的、Merlin 之前的服務(wù)器-套接字(server-socket)程序開始。在 ServerSocket 類的生存期中,其重要功能如下:

  • 接受傳入連接
  • 從客戶機(jī)讀取請(qǐng)求
  • 為請(qǐng)求提供服務(wù)

我們來(lái)考察一下以上每一個(gè)步驟,我們用代碼片段來(lái)說(shuō)明。 首先,我們創(chuàng)建一個(gè)新的 ServerSocket

ServerSocket s = new ServerSocket();
                    

接著,我們要接受傳入調(diào)用。這里,調(diào)用 accept() 應(yīng)該可以完成任務(wù),但其中有個(gè)小陷阱您得當(dāng)心:

Socket conn = s.accept( );
                    

對(duì) accept() 的調(diào)用將一直阻塞,直到服務(wù)器套接字接受了一個(gè)請(qǐng)求連接的客戶機(jī)請(qǐng)求。一旦建立了連接,服務(wù)器就使用 LineNumberReader 讀取客戶機(jī)請(qǐng)求。因?yàn)? LineNumberReader 要到緩沖區(qū)滿時(shí)才成批地讀取數(shù)據(jù),所以這個(gè)調(diào)用在讀時(shí)阻塞。 下面的片段顯示了工作中的 LineNumberReader (阻塞等等)。

InputStream in = conn.getInputStream();
                    InputStreamReader rdr = new InputStreamReader(in);
                    LineNumberReader lnr = new LineNumberReader(rdr);
                    Request req = new Request();
                    while (!req.isComplete() )
                    {
                    String s = lnr.readLine();
                    req.addLine(s);
                    }
                    

InputStream.read() 是另一種讀取數(shù)據(jù)的方式。不幸的是, read 方法也要一直阻塞到數(shù)據(jù)可用為止, write 方法也一樣,。

圖 1 描繪了服務(wù)器的典型工作過(guò)程。黑體線表示處于阻塞的操作。


圖 1. 典型的工作中的服務(wù)器
阻塞的 i/o 圖

在 JDK 1.4 之前,自由地使用線程是處理阻塞問(wèn)題最典型的辦法。但這個(gè)解決辦法會(huì)產(chǎn)生它自己的問(wèn)題 ― 即線程開銷,線程開銷同時(shí)影響性能和可伸縮性。不過(guò),隨著 Merlin 和 java.nio 包的到來(lái),一切都變了。

在下面的幾個(gè)部分中,我們將考察 java.nio 的基本思想,然后把我們所學(xué)到的一些知識(shí)應(yīng)用于修改前面描述的服務(wù)器-套接字示例。







反應(yīng)器模式(Reactor pattern)

NIO 設(shè)計(jì)背后的基石是反應(yīng)器設(shè)計(jì)模式。 分布式系統(tǒng)中的服務(wù)器應(yīng)用程序必須處理多個(gè)向它們發(fā)送服務(wù)請(qǐng)求的客戶機(jī)。然而,在調(diào)用特定的服務(wù)之前,服務(wù)器應(yīng)用程序必須將每個(gè)傳入請(qǐng)求多路分用并分派到 各自相應(yīng)的服務(wù)提供者。反應(yīng)器模式正好適用于這一功能。它允許事件驅(qū)動(dòng)應(yīng)用程序?qū)⒎?wù)請(qǐng)求多路分用并進(jìn)行分派,然后,這些服務(wù)請(qǐng)求被并發(fā)地從一個(gè)或多個(gè)客 戶機(jī)傳送到應(yīng)用程序。

反應(yīng)器模式的核心功能

  • 將事件多路分用
  • 將事件分派到各自相應(yīng)的事件處理程序

反應(yīng)器模式與觀察者模式(Observer pattern)在這個(gè)方面極為相似:當(dāng)一個(gè)主體發(fā)生改變時(shí),所有依屬體都得到通知。不過(guò),觀察者模式與單個(gè)事件源關(guān)聯(lián),而反應(yīng)器模式則與多個(gè)事件源關(guān)聯(lián)。

請(qǐng)參閱 參考資料了解關(guān)于反應(yīng)器模式的更多信息。








通道和選擇器

NIO 的非阻塞 I/O 機(jī)制是圍繞 選擇器通道構(gòu)建的。 Channel 類表示服務(wù)器和客戶機(jī)之間的一種通信機(jī)制。與反應(yīng)器模式一致, Selector 類是 Channel 的多路復(fù)用器。 Selector 類將傳入客戶機(jī)請(qǐng)求多路分用并將它們分派到各自的請(qǐng)求處理程序。

我們將仔細(xì)考察 Channel 類和 Selector 類的各個(gè)功能,以及這兩個(gè)類如何協(xié)同工作,創(chuàng)建非阻塞 I/O 實(shí)現(xiàn)。

通道做什么

通 道表示連到一個(gè)實(shí)體(例如:硬件設(shè)備、文件、網(wǎng)絡(luò)套接字或者能執(zhí)行一個(gè)或多個(gè)不同 I/O 操作(例如:讀或?qū)懀┑某绦蚪M件)的開放連接??梢援惒降仃P(guān)閉和中斷 NIO 通道。所以,如果一個(gè)線程在某條通道的 I/O 操作上阻塞時(shí),那么另一個(gè)線程可以將這條通道關(guān)閉。類似地,如果一個(gè)線程在某條通道的 I/O 操作上阻塞時(shí),那么另一個(gè)線程可以中斷這個(gè)阻塞線程。


圖 2. java.nio.channels 的類層次結(jié)構(gòu)
java.nio 包的類層次結(jié)構(gòu)

如圖 2 所示,在 java.nio.channels 包中有不少通道接口。我們主要關(guān)心 java.nio.channels.SocketChannel 接口和 java.nio.channels.ServerSocketChannel 接口。 這兩個(gè)接口可用來(lái)分別代替 java.net.Socketjava.net.ServerSocket 。盡管我們當(dāng)然將把注意力放在以非阻塞方式使用通道上,但通道可以以阻塞方式或非阻塞方式使用。

創(chuàng)建一條非阻塞通道

為了實(shí)現(xiàn)基礎(chǔ)的非阻塞套接字讀和寫操作,我們要處理兩個(gè)新類。它們是來(lái)自 java.net 包的 InetSocketAddress 類,它指定連接到哪里,以及來(lái)自 java.nio.channels 包的 SocketChannel 類,它執(zhí)行實(shí)際的讀和寫操作。

這部分中的代碼片段顯示了一種經(jīng)過(guò)修改的、非阻塞的辦法來(lái)創(chuàng)建基礎(chǔ)的服務(wù)器-套接字程序。請(qǐng)注意這些代碼樣本與第一個(gè)示例中所用的代碼之間的變化,從添加兩個(gè)新類開始:

String host = ......;
                    InetSocketAddress socketAddress = new InetSocketAddress(host, 80);
                    SocketChannel channel = SocketChannel.open();
                    channel.connect(socketAddress);
                    

緩沖區(qū)的角色

Buffer 是包含特定基本數(shù)據(jù)類型數(shù)據(jù)的抽象類。從本質(zhì)上說(shuō),它是一個(gè)包裝器,它將帶有 getter/setter 方法的固定大小的數(shù)組包裝起來(lái),這些 getter/setter 方法使得緩沖區(qū)的內(nèi)容可以被訪問(wèn)。 Buffer 類有許多子類,如下:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
ByteBuffer 是唯一支持對(duì)其它類型進(jìn)行讀寫的類,因?yàn)槠渌惗际翘囟ㄓ陬愋偷摹R坏┻B接上,就可以使用 ByteBuffer 對(duì)象從通道讀數(shù)據(jù)或?qū)?shù)據(jù)寫到通道。請(qǐng)參閱 參考資料了解關(guān)于 ByteBuffer 的更多信息。

為了使通道成為非阻塞的,我們?cè)谕ǖ郎险{(diào)用 configureBlockingMethod(false) ,如下所示:

channel.configureBlockingMethod(false);
                    

在阻塞模式中,線程將在讀或?qū)憰r(shí)阻塞,一直到讀或?qū)懖僮鲝氐淄瓿伞H绻谧x的時(shí)候,數(shù)據(jù)尚未完全到達(dá)套接字,則線程將在讀操作上阻塞,一直到數(shù)據(jù)可用。

在非阻塞模式中,線程將讀取已經(jīng)可用的數(shù)據(jù)(不論多少),然后返回執(zhí)行其它任務(wù)。如果將真(true)傳遞給 configureBlockingMethod() ,則通道的行為將與在 Socket 上進(jìn)行阻塞讀或?qū)憰r(shí)的行為完全相同。唯一的主要差別,如上所述,是這些阻塞讀和寫可以被其它線程中斷。

單靠 Channel 創(chuàng)建非阻塞 I/O 實(shí)現(xiàn)是不夠的。要實(shí)現(xiàn)非阻塞 I/O, Channel 類必須與 Selector 類配合進(jìn)行工作。

選擇器做什么

在反應(yīng)器模式情形中, Selector 類充當(dāng) Reactor 角色。 Selector 對(duì)多個(gè) SelectableChannels 的事件進(jìn)行多路復(fù)用。每個(gè) ChannelSelector 注冊(cè)事件。當(dāng)事件從客戶機(jī)處到來(lái)時(shí), Selector 將它們多路分用并將這些事件分派到相應(yīng)的 Channel 。

創(chuàng)建 Selector 最簡(jiǎn)單的辦法是使用 open() 方法,如下所示:

Selector selector = Selector.open();
                    

通道遇上選擇器

每個(gè)要為客戶機(jī)請(qǐng)求提供服務(wù)的 Channel 都必須首先創(chuàng)建一個(gè)連接。下面的代碼創(chuàng)建稱為 ServerServerSocketChannel 并將它綁定到本地端口:

ServerSocketChannel serverChannel = ServerSocketChannel.open();
                    serverChannel.configureBlocking(false);
                    InetAddress ia = InetAddress.getLocalHost();
                    InetSocketAddress isa = new InetSocketAddress(ia, port );
                    serverChannel.socket().bind(isa);
                    

每個(gè)要為客戶機(jī)請(qǐng)求提供服務(wù)的 Channel 都必須接著將自己向 Selector 注冊(cè)。 Channel 應(yīng)根據(jù)它將處理的事件進(jìn)行注冊(cè)。例如,接受傳入連接的 Channel 應(yīng)這樣注冊(cè),如下:

SelectionKey acceptKey =
                    channel.register( selector,SelectionKey.OP_ACCEPT);
                    

ChannelSelector 的注冊(cè)用 SelectionKey 對(duì)象表示。滿足以下三個(gè)條件之一, Key 就失效:

  • Channel 被關(guān)閉。
  • Selector 被關(guān)閉。
  • 通過(guò)調(diào)用 Keycancel() 方法將 Key 本身取消。

Selectorselect() 調(diào)用時(shí)阻塞。接著,它開始等待,直到建立了一個(gè)新的連接,或者另一個(gè)線程將它喚醒,或者另一個(gè)線程將原來(lái)的阻塞線程中斷。

注冊(cè)服務(wù)器

Server 是那個(gè)將自己向 Selector 注冊(cè)以接受所有傳入連接的 ServerSocketChannel ,如下所示:

SelectionKey acceptKey = serverChannel.register(sel, SelectionKey.OP_ACCEPT);
                    while (acceptKey.selector().select() > 0 ){
                    ......
                    

Server 被注冊(cè)后,我們根據(jù)每個(gè)關(guān)鍵字(key)的類型以迭代方式對(duì)一組關(guān)鍵字進(jìn)行處理。一個(gè)關(guān)鍵字被處理完成后,就都被從就緒關(guān)鍵字(ready keys)列表中除去,如下所示:

Set readyKeys = sel.selectedKeys();
                    Iterator it = readyKeys.iterator();
                    while (it.hasNext())
                    {
                    SelectionKey key = (SelectionKey)it.next();
                    it.remove();
                    ....
                    ....
                    ....
                    }
                    

如果關(guān)鍵字是可接受(acceptable)的,則接受連接,注冊(cè)通道,以接受更多的事件(例如:讀或?qū)懖僮鳎?如果關(guān)鍵字是可讀的(readable)或可寫的(writable),則服務(wù)器會(huì)指示它已經(jīng)就緒于讀寫本端數(shù)據(jù):

SocketChannel socket;
                    if (key.isAcceptable()) {
                    System.out.println("Acceptable Key");
                    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                    socket = (SocketChannel) ssc.accept();
                    socket.configureBlocking(false);
                    SelectionKey another =
                    socket.register(sel,SelectionKey.OP_READ|SelectionKey.OP_WRITE);
                    }
                    if (key.isReadable()) {
                    System.out.println("Readable Key");
                    String ret = readMessage(key);
                    if (ret.length() > 0) {
                    writeMessage(socket,ret);
                    }
                    }
                    if (key.isWritable()) {
                    System.out.println("Writable Key");
                    String ret = readMessage(key);
                    socket = (SocketChannel)key.channel();
                    if (result.length() > 0 ) {
                    writeMessage(socket,ret);
                    }
                    }
                    








唵嘛呢叭咪吽 — 非阻塞服務(wù)器套接字快顯靈!

對(duì) JDK 1.4 中的非阻塞 I/O 的介紹的最后一部分留給您:運(yùn)行這個(gè)示例。

在這個(gè)簡(jiǎn)單的非阻塞服務(wù)器-套接字示例中,服務(wù)器讀取發(fā)送自客戶機(jī)的文件名,顯示該文件的內(nèi)容,然后將內(nèi)容寫回到客戶機(jī)。

這里是您運(yùn)行這個(gè)示例需要做的事情:

  1. 安裝 JDK 1.4(請(qǐng)參閱 參考資料)。
  2. 將兩個(gè) 源代碼文件復(fù)制到您的目錄。
  3. 編譯和運(yùn)行服務(wù)器, java NonBlockingServer 。
  4. 編譯和運(yùn)行客戶機(jī), java Client
  5. 輸入類文件所在目錄的一個(gè)文本文件或 java 文件的名稱。
  6. 服務(wù)器將讀取該文件并將其內(nèi)容發(fā)送到客戶機(jī)。
  7. 客戶機(jī)將把從服務(wù)器接收到的數(shù)據(jù)打印出來(lái)。(由于所用的 ByteBuffer 的限制,所以將只讀取 1024 字節(jié)。)
  8. 輸入 quit 或 shutdown 命令關(guān)閉客戶機(jī)。






結(jié)束語(yǔ)

Merlin 的新 I/O 包覆蓋范圍很廣。Merlin 的新的非阻塞 I/O 實(shí)現(xiàn)的主要優(yōu)點(diǎn)有兩方面:線程不再在讀或?qū)憰r(shí)阻塞,以及 Selector 能夠處理多個(gè)連接,從而大幅降低了服務(wù)器應(yīng)用程序開銷。

我們已經(jīng)著重論述了新的 java.nio 包的這兩大優(yōu)點(diǎn)。我們希望,您將把在這里所學(xué)到的知識(shí)應(yīng)用到自己的實(shí)際應(yīng)用程序開發(fā)工作中。



參考資料

  • 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文.

  • 請(qǐng)單擊本文頂部或底部的 討論參加本文的 討論論壇。


  • 下載 源代碼運(yùn)行本文的示例。


  • 安裝 JDK 1.4。


  • NIO 主頁(yè)上了解所有新的非阻塞 I/O API。


  • 要更多了解對(duì) JDK 1.4 的一般介紹,請(qǐng)參閱 Java 2 platform, Standard Edition, v1.4 overview。


  • 請(qǐng)?jiān)L問(wèn) Java Community Process,了解 java.nio 包成為 Java 平臺(tái)的一部分的過(guò)程。


  • 要了解對(duì) Java 平臺(tái)長(zhǎng)期存在的線程開銷問(wèn)題的深入討論方面的內(nèi)容,請(qǐng)參閱 Allen Holub 的 proposal for fixing the Java platform‘s threading problems,其中有一部分討論 Java I/O( developerWorks,2000 年 10 月)。


  • 了解關(guān)于 ByteBuffer 類的更多信息。


  • 獲取關(guān)于 Reactor pattern的更多信息。


  • John Zukowski 的 Magic with Merlin 專欄講述的都是 Merlin 給 Java 平臺(tái)帶來(lái)的變化。


  • JavaWorld上的文章“ Master Merlin‘s new I/O classes”告訴您如何從非阻塞 I/O 和內(nèi)存映射緩沖區(qū)獲取最多性能。


  • 想探索一下作為一名開發(fā)者有哪些選擇嗎?請(qǐng)從 developerWorksdeveloper kits from IBM完整清單開始。


  • 您可在 developerWorksJava 技術(shù)專區(qū)找到數(shù)以百計(jì)的關(guān)于 Java 編程的各個(gè)方面的文章。


作者簡(jiǎn)介

 

Aruna Kalagnanam 是 IBM India Lab 電子商務(wù)集成技術(shù)方面的軟件工程師。您可以通過(guò) kaaruna@in.ibm.com與 Aruna 聯(lián)系。


 

Balu G 是 IBM India Lab 電子商務(wù)集成技術(shù)方面的軟件工程師??梢酝ㄟ^(guò) gbalu@in.ibm.com與 Balu 聯(lián)系。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多