NIO(四)channel總結

加瓦一枚發表於2018-12-26

Java NIO Channel

通常來說, 所有的 NIO 的 I/O 操作都是從 Channel 開始的. 一個 channel 類似於一個 stream.
java Stream 和 NIO Channel 對比

  • 我們可以在同一個 Channel 中執行讀和寫操作, 然而同一個 Stream 僅僅支援讀或寫.

  • Channel 可以非同步地讀寫, 而 Stream 是阻塞的同步讀寫.

  • Channel 總是從 Buffer 中讀取資料, 或將資料寫入到 Buffer 中.

Channel 型別有:

  • FileChannel, 檔案操作

  • DatagramChannel, UDP 操作

  • SocketChannel, TCP 操作

  • ServerSocketChannel, TCP 操作, 使用在伺服器端.
    這些通道涵蓋了 UDP 和 TCP網路 IO以及檔案 IO.

基本的 Channel 使用例子:

public static void main( String[] args ) throws Exception
{
    RandomAccessFile aFile = new RandomAccessFile("/Users/xiongyongshun/settings.xml", "rw");
    FileChannel inChannel = aFile.getChannel();

    ByteBuffer buf = ByteBuffer.allocate(48);

    int bytesRead = inChannel.read(buf);
    while (bytesRead != -1) {
        buf.flip();

        while(buf.hasRemaining()){
            System.out.print((char) buf.get());
        }

        buf.clear();
        bytesRead = inChannel.read(buf);
    }
    aFile.close();
}

FileChannel

FileChannel 是操作檔案的Channel, 我們可以通過 FileChannel 從一個檔案中讀取資料, 也可以將資料寫入到檔案中.
注意, FileChannel 不能設定為非阻塞模式.

開啟 FileChannel

RandomAccessFile aFile     = new RandomAccessFile("test.txt", "rw");
FileChannel      inChannel = aFile.getChannel();

從 FileChannel 中讀取資料

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

寫入資料

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);
}

關閉

當我們對 FileChannel 的操作完成後, 必須將其關閉

channel.close();

設定 position

long pos channel.position();
channel.position(pos +123);

檔案大小

我們可以通過 channel.size()獲取關聯到這個 Channel 中的檔案的大小. 注意, 這裡返回的是檔案的大小, 而不是 Channel 中剩餘的元素個數.

截斷檔案

channel.truncate(1024);

將檔案的大小截斷為1024位元組.

強制寫入

我們可以強制將快取的未寫入的資料寫入到檔案中:

channel.force(true);

SocketChannel

SocketChannel 是一個客戶端用來進行 TCP 連線的 Channel.
建立一個 SocketChannel 的方法有兩種:

  • 開啟一個 SocketChannel, 然後將其連線到某個伺服器中

  • 當一個 ServerSocketChannel 接受到連線請求時, 會返回一個 SocketChannel 物件.

開啟 SocketChannel

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

關閉

socketChannel.close();

讀取資料

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

如果 read()返回 -1, 那麼表示連線中斷了.

寫入資料

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);
}

非阻塞模式

我們可以設定 SocketChannel 為非同步模式, 這樣我們的 connect, read, write 都是非同步的了.

連線

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://example.com", 80));

while(! socketChannel.finishConnect() ){
    //wait, or do something else...    
}

在非同步模式中, 或許連線還沒有建立, connect 方法就返回了, 因此我們需要檢查當前是否是連線到了主機, 因此通過一個 while 迴圈來判斷.

讀寫

在非同步模式下, 讀寫的方式是一樣的.
在讀取時, 因為是非同步的, 因此我們必須檢查 read 的返回值, 來判斷當前是否讀取到了資料.

ServerSocketChannel

ServerSocketChannel 顧名思義, 是用在伺服器為端的, 可以監聽客戶端的 TCP 連線, 例如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

開啟 關閉

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.close();

監聽連線

我們可以使用ServerSocketChannel.accept()方法來監聽客戶端的 TCP 連線請求, accept()方法會阻塞, 直到有連線到來, 當有連線時, 這個方法會返回一個 SocketChannel 物件:

while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

非阻塞模式

在非阻塞模式下, accept()是非阻塞的, 因此如果此時沒有連線到來, 那麼 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...
        }
}

DatagramChannel

DatagramChannel 是用來處理 UDP 連線的.

開啟

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

讀取資料

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

channel.receive(buf);

傳送資料

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("example.com", 80));

連線到指定地址

因為 UDP 是非連線的, 因此這個的 connect 並不是向 TCP 一樣真正意義上的連線, 而是它會講 DatagramChannel 鎖住, 因此我們僅僅可以從指定的地址中讀取或寫入資料.

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

本文連結為: segmentfault.com/a/1190000006824107

相關文章