乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Merlin 給 Java 平臺帶來了非阻塞 I/O

       shaobin0604@163.com 2007-11-29


      2002 年 3 月 12 日

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

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

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

      Merlin 之前的 I/O 編程

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

      • 接受傳入連接
      • 從客戶機讀取請求
      • 為請求提供服務

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

      ServerSocket s = new ServerSocket();
                          

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

      Socket conn = s.accept( );
                          

      accept() 的調(diào)用將一直阻塞,直到服務器套接字接受了一個請求連接的客戶機請求。一旦建立了連接,服務器就使用 LineNumberReader 讀取客戶機請求。因為 LineNumberReader 要到緩沖區(qū)滿時才成批地讀取數(shù)據(jù),所以這個調(diào)用在讀時阻塞。 下面的片段顯示了工作中的 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 描繪了服務器的典型工作過程。黑體線表示處于阻塞的操作。


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

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

      在下面的幾個部分中,我們將考察 java.nio 的基本思想,然后把我們所學到的一些知識應用于修改前面描述的服務器-套接字示例。







      反應器模式(Reactor pattern)

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

      反應器模式的核心功能

      • 將事件多路分用
      • 將事件分派到各自相應的事件處理程序

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

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








      通道和選擇器

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

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

      通道做什么

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


      圖 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 接口。 這兩個接口可用來分別代替 java.net.Socketjava.net.ServerSocket 。盡管我們當然將把注意力放在以非阻塞方式使用通道上,但通道可以以阻塞方式或非阻塞方式使用。

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

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

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

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

      緩沖區(qū)的角色

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

      • ByteBuffer
      • CharBuffer
      • DoubleBuffer
      • FloatBuffer
      • IntBuffer
      • LongBuffer
      • ShortBuffer
      ByteBuffer 是唯一支持對其它類型進行讀寫的類,因為其它類都是特定于類型的。一旦連接上,就可以使用 ByteBuffer 對象從通道讀數(shù)據(jù)或?qū)?shù)據(jù)寫到通道。請參閱 參考資料了解關(guān)于 ByteBuffer 的更多信息。

      為了使通道成為非阻塞的,我們在通道上調(diào)用 configureBlockingMethod(false) ,如下所示:

      channel.configureBlockingMethod(false);
                          

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

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

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

      選擇器做什么

      在反應器模式情形中, Selector 類充當 Reactor 角色。 Selector 對多個 SelectableChannels 的事件進行多路復用。每個 ChannelSelector 注冊事件。當事件從客戶機處到來時, Selector 將它們多路分用并將這些事件分派到相應的 Channel 。

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

      Selector selector = Selector.open();
                          

      通道遇上選擇器

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

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

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

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

      ChannelSelector 的注冊用 SelectionKey 對象表示。滿足以下三個條件之一, Key 就失效:

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

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

      注冊服務器

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

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

      Server 被注冊后,我們根據(jù)每個關(guān)鍵字(key)的類型以迭代方式對一組關(guān)鍵字進行處理。一個關(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)的,則接受連接,注冊通道,以接受更多的事件(例如:讀或?qū)懖僮鳎?如果關(guān)鍵字是可讀的(readable)或可寫的(writable),則服務器會指示它已經(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);
                          }
                          }
                          








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

      對 JDK 1.4 中的非阻塞 I/O 的介紹的最后一部分留給您:運行這個示例。

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

      這里是您運行這個示例需要做的事情:

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






      結(jié)束語

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

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



      參考資料



      作者簡介

       

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


       

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

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多