1. 程式人生 > >Java NIO — 通道(Channel)

Java NIO — 通道(Channel)

NIO 中主要的三個概念為緩衝區、通道、選擇器,它們之間的關係如下所示:

此處要提醒的是,JDK 1.7 升級了 NIO 類庫,升級後的 NIO 類庫被稱為 NIO2.0。在 NIO2.0 中,提供了非同步檔案I/O操作,同時提供了與 UNIX 網路程式設計事件驅動I/O對應的 AIO。

在之前《Java NIO 緩衝區》一文中已經介紹過緩衝區的相關知識,本文主要介紹通道的使用。

通道(Channel)

Channel 用於緩衝區與檔案或套接字之間有效的傳輸資料。傳統的字元/位元組流不同的讀寫操作是分開的,對應於 InputStream 與 OutputStream 兩個類,而 Channel 同時支援讀寫操作,可以更好的對映底層作業系統的API。

Channel 在 Java 中是一個介面,裡面只有兩個方法,用來檢查通道是否開啟與關閉通道:

public interface Channel extends Closeable {

    public boolean isOpen();

    public void close() throws IOException;

}

Channel介面與其主要實現類的結構圖如下所示:

  • ReadableByteChannel:支援讀取位元組的通道
  • WritableByteChannel:支援寫入位元組的通道
  • NetworkChannel:NIO 2.0 新增,用於加強對 Socket 的操作
  • SelectableChannel:支援通過 Selector 實現多路複用的通道

可以看到,FileChannel、SocketChannel、DatagramChannel 都實現了 ReadableByteChannel 與 WritableByteChannel 介面,因此它們都同時支援讀寫操作。

此外,對於圖中的四種Channel來說,它們都是執行緒安全的。

  • FileChannel:檔案通道,可以通過在 RandomAccessFile、FileInputStream、FileOutputStream 物件上呼叫 getChannel() 方法獲取,在NIO 2.0 中可以通過 open() 方法直接開啟檔案建立檔案通道
  • SocketChannel:通過靜態 open() 方法建立。對應 Socket,通過 socket() 方法獲得與之關聯的 socket 物件
  • ServerSocketChannel:通過靜態 open() 方法建立。對應 ServerSocket,通過 accept() 方法接收請求,並返回 SocketChannel 物件
  • DatagramChannel:通過靜態 open() 方法建立。對應 DatagramSocket,用於 UDP 資料包的傳輸

通道可以設定為阻塞或非阻塞模式,預設是阻塞模式,可以通過 configureBlocking(false) 方法設定為非阻塞模式。

要注意的是,FileChannel 只能執行在阻塞模式下,其它通道可以在阻塞和非阻塞模式之間選擇。通道需要和位元組緩衝區配合使用。

ServerSocketChannel(伺服器端)示例:

public class SSCDemo {

    public static void run() throws IOException, InterruptedException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(1212));
        while (true) {
            System.out.println("正在等待連線...");
            SocketChannel sc = ssc.accept();
            if (sc == null)
                TimeUnit.SECONDS.sleep(2);
            else {
                System.out.println("新連線接入:" + sc.getRemoteAddress());
                sc.read(byteBuffer);
                byteBuffer.flip();
                System.out.print("接收資料:");
                while (byteBuffer.hasRemaining()) {
                    System.out.print((char) byteBuffer.get());
                }
                System.out.println();
                byteBuffer.clear();
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        SSCDemo.run();
    }
}

SocketChannel(客戶端)示例:

public class SCDemo {

    public static void run() throws IOException, InterruptedException {
        byte[] array = "Hello World!".getBytes();
        ByteBuffer byteBuffer = ByteBuffer.wrap(array);
        SocketChannel sc = SocketChannel.open();
        sc.configureBlocking(false);
        sc.bind(new InetSocketAddress(1213));
        sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 1212));
        while (!sc.finishConnect()) {
            System.out.println("連線中...");
            TimeUnit.SECONDS.sleep(2);
        }
        System.out.println("連線完成!");
        sc.write(byteBuffer);
        sc.close();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        SCDemo.run();
    }
}

執行結果(伺服器端):

正在等待連線...
正在等待連線...
正在等待連線...
新連線接入:/169.254.77.64:1213
接收資料:Hello World!
正在等待連線...
正在等待連線...