首頁  >  文章  >  Java  >  Java中關於非阻塞I/O的使用方法

Java中關於非阻塞I/O的使用方法

黄舟
黄舟原創
2017-09-29 09:58:371478瀏覽

這篇文章主要介紹了Java 非阻塞I/O使用方法,文中涉及非阻塞I/O的簡介,同時向大家展示了利用非阻塞I/O實現客戶端的方法,需要的朋友可以參考下。

絕大部分知識與實例來自O'REILLY的《Java網絡程式設計》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O'REILLY))。

非阻塞I/O簡介

非阻塞I/O(NIO)是處理高並發的一種手段。在高並發的情況下,創建和回收線程以及在線程間切換的開銷變得不容忽視,此時就可以使用非阻塞I/O技術。這種技術的核心思想是每次選取一個準備好的連接,盡快填入這個連接所能管理的盡可能多的數據,然後轉向下一個準備好的連接。

利用非阻塞I/O實作的客戶端

#一般情況下,客戶端不會需要處理很高數量的並發連接。事實上,非阻塞I/O主要是為伺服器設計的,但它也可以用在客戶端。由於客戶端的設計相比伺服器容易,因此以下先用客戶端來進行簡單示範。

首先介紹通道(channel)和緩衝區。非阻塞I/O中使用SocketChannel類別建立連線。要取得SocketChannel對象,需要將SocketAddress物件(通常會使用它的子類別InetSocketAddress)傳入它的靜態工廠方法open()。下面為一個範例:


SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
SocketChannel client = SocketChannel.open(address);

open()方法是阻塞的,因此這之後的程式碼在連線建立之前不會執行。如果連線無法建立,會拋出一個IOException異常。

連接建立之後就需要取得輸入和輸出。有別於傳統的getInputStream()與getOutputStream(),利用頻道,你可以直接寫入頻道本身。不是寫入一個位元組數組,而是要寫入一個ByteBuffer物件。 ByteBuffer物件透過ByteBuffer.allocate(int capacity)取得,capacity為緩衝區大小,單位為位元組:


##

ByteBuffer buffer = ByteBuffer.allocate(74);

取得ByteBuffer物件後,將其傳遞給SocketChannel物件的read()方法,SocketChannel物件會用從Socket讀取的資料填入這個緩衝區。 read()方法傳回成功讀取並儲存在緩衝區中的位元組數。預設情況下,它會至少讀取一個字節,或返回-1指示資料結束,沒有字節可用時阻塞。這與InputStream的行為大致相同。但如果設定成非阻塞模式,沒有位元組可用時它會立即回傳0,不會阻塞。


現在假定緩衝區內已經有了一些數據,之後就需要將它們提取出來。可以使用傳統的方式,先將資料寫入一個位元組數組,之後再寫入一個輸出流。這裡介紹一種完全基於通道的方法:利用Channels工具類別將輸出流封裝到一個通道中:


#

WritableByteChannel out = Channels.newChannel(System.out);

上面的程式碼將System.out封裝入一個通道中。這之後就可以進行輸出了。 ByteBuffer物件在每次輸出之前,需要呼叫它的flip()方法,使得通道從開頭開始讀取。讀寫完畢後,還需要呼叫它的clear()方法,重置緩衝區的狀態。以下是進行一次資料輸出的程式碼:


buffer.flip();
out.write(buffer);
buffer.clear();

實例1:利用非阻塞I/O實作的CharGenerator(字元產生器)用戶端

伺服器程式碼:


public static void createCharGeneratorServer(){
  try(ServerSocket server = new ServerSocket(19)){
    while(true){
      try(Socket connection = server.accept()){
        OutputStream out = connection.getOutputStream();
        int firstPrintableCharacter = 33;
        int numberOfPrintableCharacter = 94;
        int numberOfCharactersPerLine = 72;
        int start = firstPrintableCharacter;
        while(true){
          for(int i = start ;
              i < start + numberOfCharactersPerLine ; i++){
            out.write
            (firstPrintableCharacter + (i - firstPrintableCharacter) % numberOfPrintableCharacter);
          }
          out.write(&#39;\r&#39;);
          out.write(&#39;\n&#39;);
          start = firstPrintableCharacter + (start + 1 - firstPrintableCharacter) % numberOfPrintableCharacter;
        }
      }catch (IOException e) {
        e.printStackTrace();
      }
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
}

用戶端程式碼:


try {
  SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
  SocketChannel client = SocketChannel.open(address);
  ByteBuffer buffer = ByteBuffer.allocate(74);
    WritableByteChannel out = Channels.newChannel(System.out);
  while(client.read(buffer) != -1){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }
} catch (IOException e) {
  e.printStackTrace();
}
输出(无限循环):
]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEF
^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEFG
_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEFGH
`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEFGHI
abcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEFGHIJ
bcdefghijklmnopqrstuvwxyz{|}~!"#$%&&#39;()*+,-./0123456789:;<=>?@ABCDEFGHIJK

啟用非阻塞模式

上面的程式和使用輸入/輸出流的傳統方式並沒有太大差異。不過,可以呼叫ServerSocket的configureBlocking(false)方法將其設定為非阻塞模式。在這個模式下,如果沒有可用的數據,read()方法會立即返回,這讓客戶可以去做其他事情。不過,由於read()方法在讀不到資料時會回傳0,讀取資料的迴圈需要做一些改變:


while(true){
  //这里可以写每次循环都要做的事,无论有没有读到数据
  int n = client.read(buffer);
  if(n > 0){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }else if (n == -1) {
    //除非服务器故障,否则不会发生
    break;
  }
}

總結

以上是Java中關於非阻塞I/O的使用方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn