Java NIO學習系列二:Channel


  上文總結了Java NIO中的Buffer相關知識點,本文中我們來總結一下它的好兄弟:Channel。上文有說到,Java NIO中的Buffer一般和Channel配對使用,NIO中的所有IO都起始於一個Channel,一個Channel就相當於一個流,,可以從Channel中讀取數據到Buffer,或者寫數據到Channel中。

  Channel簡介

  FileChannel

  SocketChannel

  ServerSocketChannel

  DatagramChannel

  總結

 

1. Channel簡介

  Java NIO中的Channel類似流,但是有一些不同:

  • Channel既可以支持寫也可以支持讀,而流則是單向的,只能支持寫或者讀;
  • Channel支持異步讀寫;
  • Channel一般和Buffer配套使用,從Channel中讀取數據到Buffer中,或從Buffer寫入到Channel中;

  Channel的主要實現類有如下幾種:

  • FileChannel,可以對文件讀/寫數據;
  • DatagramChannel,通過UDP從網絡讀/寫數據;
  • SocketChannel,通過TCP從網絡讀/寫數據;
  • ServerSocketChannel,允許你監聽TCP連接,對於每個TCP連接都創建一個SocketChannel;

 

2. FileChannel

  Java NIO FileChannel是一類文件相連的channel,通過它可以從文件讀取數據,或向文件寫數據。FileChannel類是Java NIO類庫提供的用於代替標准Java IO API來讀寫文件。FileChannel不能設置為非阻塞模式,只能工作在阻塞模式下。

2.1 打開FileChannel

  在使用FileChannel之前先要打開它,就I/O類庫中有三個類被修改了,用以產生FileChannel,這三個類是:InputStream、OutputStream、RandomAccessFile,如下是如何從RandomAccessFile獲取FileChannel:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

2.2 從FileChannel讀取數據

  首先需要分配一個Buffer,從FileChannel讀取的數據會讀到Buffer中(是不是有點繞)。然后調用FileChannel的read()方法來讀數據,這個方法會把數據讀到Buffer中,返回的int代表讀取了多少字節,返回-1代表文件末尾。

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

2.3 往FileChannel寫數據

  通過調用FileChannel的write()方法可以往其中寫數據,參數是一個Buffer:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }

  這里write()方法也沒有保證一次寫入多少數據,我們只是不斷重復直到寫完。

2.4 關閉FileChannel

  用完FileChannel之后記得要將其關閉:

channel.close();

2.5 FileChannel位置

  調用FileChannel對象的position()方法可以獲取其position,也可以調用position(long pos)設置其position:

// 獲取position
long pos channel.position();
// 設置position
channel.position(pos +123);

  如果將position設置到文件末尾后面,然后嘗試讀取文件,會返回-1;

  如果將position設置到文件末尾后面,然后嘗試向文件中寫數據,則文件會自動擴展,並且從設置的position位置處開始寫數據,這會導致“file hole”,即物理文件會有間隙。

2.6 FileChannel尺寸

  size()方法返回filechannel連接的文件的尺寸大小:

long fileSize = channel.size(); 

2.7 截短FileChannel

  truncate()方法可以截短文件:

channel.truncate(1024);

  如上,將文件截取為1024字節長度。

2.8 FileChannel Force

  FileChannel.force()方法會刷新所有未寫入到磁盤的數據到磁盤上。因為操作系統會先將數據緩存到內存中,再一次性寫入磁盤,所以不能保證寫到channel中的數據是否寫入到磁盤上,所以可以調用flush()方法強制將數據寫入磁盤。

  force()方法有一個boolean參數,代表是否要寫入文件的元數據(比如權限):

channel.force(true);

 

3. SocketChannel

  Java NIO SocketChannel用於和TCP網絡socket相連,等同於Java網絡包中的Socket。可以通過兩種方式來創建一個SocketChannel:

  • 打開一個SocketChannel並且將其和網絡上的某台服務器相連;
  • 當有一個連接到達一個ServerSocketChannel時會自動創建一個SocketChannel;

3.1 打開Socket通道

  如下示例說明了如何打開一個SocketChannel:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

3.2 關閉Socket通道

  對於這種資源類的使用,是要記得及時關閉的:

socketChannel.close();

3.3 從Socket通道讀數據

  調用read()方法可以讀取數據:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

3.4 往Socket通道寫數據

  調用其寫方法write()可以向其中寫數據,使用一個Buffer作為其參數:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }

3.5 工作在非阻塞模式下

  可以將SocketChannel設置為非阻塞模式,設置之后可以異步地調用其connect()、read()和write()方法。

connect()

  對於處於非阻塞模式下的SocketChannel,調用其connect()方法之后會立即返回,即使沒有成功建立連接。可以調用finishConnect()方法來獲知是否成功建立連接:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }

write()

  對於處於非阻塞模式的SocketChannel,調用其write()也會返回,但是可能還沒有寫入數據。因此你需要在一個循環中調用它,就像前面的例子中看到的那樣。

read()

  對於處於非阻塞模式的SocketChannel,調用其read()有可能出現返回int值,但是還沒有任何數據讀入到buffer中。因此需要關注返回int值,這個可以告訴我們有多少數據是已經讀取的。

非阻塞模式下和Selectors一起工作

  非阻塞模式下的SocketChannel適合和Selector一起搭配使用。通過往Selector中注冊一個或多個SocketChannel,可以通過Selector選擇已經就緒的Channel。具體使用稍后會詳述。

 

4. ServerSocketChannel

  Java NIO ServerSocketChannel可以監聽TCP連接,就像標准Java網絡庫中的ServerSocket。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

4.1 打開ServerSocketChannel

  很簡單,調用其open()方法就可以開啟一個ServerSocketChannel:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

4.2 關閉ServerSocketChannel

serverSocketChannel.close();

4.3 監聽連接

  調用其accept()方法之后可以監聽連接。在一個新的連接到來之前,都處於阻塞狀態,一旦新的連接到來,accept()方法將返回一個和這個連接對應的SocketChannel。

  將其放在一個while循環中就可以不斷監聽新的連接:

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

  當然實際中while循環應該使用其他的判斷條件而不是true。

4.4 工作在非阻塞模式下

  ServerSocketChannel同樣可設置為非阻塞模式,此時調用其accept()會立即返回,如果沒有新的連接可能返回null,需要對返回值是否為空進行校驗:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ //do something with socketChannel...  } }

 

5. DatagramChannel

  DatagramChannel用於發送和接收UDP包。因為UDP是一個無連接協議,不是像其他channel一樣進行讀寫操作,而是通過數據包來交換數據。

5.1 打開DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

  這個例子中打開了一個DatagramChannel,可以通過端口9999接收UDP數據包。

5.2 接收數據

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

  調用DatagramChannel的receive()方法可以將接收到的數據包中的數據復制到指定的Buffer中。如果收到的數據大於Buffer容量,默認將其拋棄。

5.3 發送數據

String newData = "New String to write to file..."
                    + System.currentTimeMillis();    
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

  在這個例子中是向“jenkov.com”服務器的80端口發送UDP數據包。因為UDP不保證數據傳輸,所以也不會知道發送的數據包是否被收到。

5.4 連接到指定地址

  可以將DatagramChannel“連接”到一個指定的網絡地址。因為UDP協議是無連接的,所以通過這種方式創建連接並不會像基於TCP的channel那樣創建一個真實的連接。但是,這樣可以“鎖定”這個DatagramChannel,使得它只能和一個指定的ip地址交換數據。

channel.connect(new InetSocketAddress("jenkov.com", 80)); 

  在這種情況下(鎖定),也可以像其他Channel那樣調用read()和write()方法,只不過不能夠保證數據一定能夠收到。

int bytesRead = channel.read(buf); 
int bytesWritten = channel.write(buf);

 

6. 總結

  本文主要總結了Channel的相關知識,Channel是通道,和Buffer進行交互數據,可以讀數據到Buffer中,也可以從Buffer往Channel寫數據。Channel主要有下面幾種:

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

  其中FileChannel是和文件交互、SocketChannel和ServerSocketChannel是基於TCP的網絡Channel,DatagramChannel是基於UDP的網絡Channel。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM