一個channel(通道)表示一個連接,一個channel可以表示一個底層的文件描述符,比如有硬體設備、文件、網絡套接字,或者是執行IO的讀寫操作。在Java NIO中有不同的Channel實現。
這裡簡單介紹主要Channel的使用,如果想詳細的了解,可以像下圖一樣了解整體的設計結構。
NIO中主要的channel實現如下:
FileChannel:文件通道,用於文件數據的讀寫。
DatagramChannel:數據報通道,可以通過UDP協議讀取網絡中的數據。
SocketChannel:套接字通道,用於Socket套接字TCP網絡連接的數據讀寫。
ServerSocketChannel:伺服器監聽通道,可以監聽TCP連接請求,對每個監聽進來的連接都創建一個SocketChannel套接字通道。
public static void fileChannelTest() { RandomAccessFile raf = null; FileChannel fileChannel = null; try { raf = new RandomAccessFile("/usr/local/tools/a.txt", "rw"); //創建一個RandomAccessFile對象,傳入一個文件路徑,支持讀寫模式
//獲取文件通道 fileChannel = raf.getChannel();
//創建字節緩衝區,用於讀取數據和寫入數據 ByteBuffer buffer = ByteBuffer.allocate(48);
//從文件讀取輸出控制臺 while (fileChannel.read(buffer) != -1) { buffer.flip(); String str = new String(buffer.array(), buffer.position(), buffer.limit(), "utf-8"); System.err.print(str); buffer.clear(); }
//寫數據 buffer.put("\n新信息!!!".getBytes("UTF-8")); //切換讀模式 buffer.flip(); //通道寫數據 fileChannel.write(buffer);
} catch (Exception e) { e.printStackTrace(); } finally { try { raf.close(); fileChannel.close(); } catch (IOException e) { e.printStackTrace(); } }}
要注意的是在ByteBuffer的模式切換,新創建的ByteBuffer模式是寫模式。如果這時想從通道讀數據,可以作為參數傳進去,從通道讀取的數據寫入到ByteBuffer。
在往通道寫數據時,需要將ByteBuffer切換為讀模式,作為參數傳入輸出Channel,從ByteBuffer讀取數據寫入輸出Channel。
SocketChannel套接字通道
NIO中的SocketChannel是一個連接到TCP網絡套接字的通道。可以通過以下2種方式創建SocketChannel,打開一個SocketChannel並連接到網際網路上的某臺伺服器,一個新連接到達ServerSocketChannel時,會創建一個SocketChannel。
ServerSocketChannel用於伺服器端,SocketChannel同時用於服務端和客戶端,換句話說,對應一個連接,兩端都有一個負責傳輸的SocketChannel傳輸通道。
SocketChannel支持阻塞和非阻塞模式,通過socketChannel.configureBlocking(true)設置,默認是非阻塞的。在阻塞模式下, Socket Channel通道的 connect、read、 write操作,都是同步的和阻塞式的,在效率上與Java舊的OIO的面向流的阻塞式讀寫操作相同。
Java NIO中的 ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道, 就像標準IO中的ServerSocket一樣。ServerSocketChannel類在 java.nio.channels包中。
創建連接通道
//獲得一個通道SocketChannel socketChannel = SocketChannel.open();//設置非阻塞模式socketChannel.configureBlocking(true);//發起連接socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
//在阻塞模式下可能還沒有建立連接,一直不挺的自旋while (!socketChannel.finishConnect()) {
}
SocketChannel寫數據
String newData = "abcde" + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);buf.put(newData.getBytes());
buf.flip();while(buf.hasRemaining()) { socketChannel.write(buf);}
SocketChannel讀取數據
ByteBuffer buf1 = ByteBuffer.allocate(48);int bytesRead = socketChannel.read(buf1);讀取是異步的,因此我們必須檢查read的返回值,以便判斷當前是否讀取到了數據。read()方法的返回值,是讀取的字節數。如果返回-1,那麼表示讀取到對方的輸出結束標誌,對方已經輸出結束,準備關閉連接。實際上在讀取數據時還是需要Selector選擇器。
關閉SocketChannel
在關閉 Socketchannel傳輸通道前,如果傳輸通道用來寫入數據,建議調用一次 shutdownOutput終止輸出方法,向對方發送一個輸出的結束標誌-1。然後調用 socketchannel.close()方法,關閉套接字連接。
//輸出結束標識socketChannel.shutdownOutput();//關閉套接字socketChannel.close();DatagramChannel數據報通道NIO中的DatagramChannel是一個能收發UDP包的通道。因為UDP是無連接的網絡協議,所以不能像其它通道那樣讀取和寫入。它發送和接收的是數據包。使用 DatagramChannel數據報通道來處理UDP協議的數據傳輸。
打開通道
打開的 DatagramChannel可以在UDP埠9000上接收數據包
DatagramChannel datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);datagramChannel.socket().bind(new InetSocketAddress(9000));
從數據報通道讀取數據
可以從DatagramChannel通道讀取數據,和前面的 Socketchannel的讀取方式不同,不是調用read方法,而是調用receive( Byte Bufferbuf)方法將數據從 Datagramchannel讀入,再寫入到ByteBuffer緩衝區中。
ByteBuffer buf2 = ByteBuffer.allocate(48);buf2.clear();datagramChannel.receive(buf2);
寫數據到數據報通道
向 DatagramChannel發送數據,和向 SocketChannel通道發送數據的方法也是不同的。這裡不是調用 write方法,而是調用send方法。
String strData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf3 = ByteBuffer.allocate(48);buf3.put(newData.getBytes());buf3.flip();datagramChannel.send(buf, new InetSocketAddress("127.0.0.1", 9000));UDP是面向非連接的協議,因此,在調用send方法發送數據的時候,需要指定接收方的地址(IP和埠)
參考資料
http://ifeve.com/datagram-channel/
http://ifeve.com/socket-channel/