Java NIO有三個核心的元件:Buffer、Channel和Selector。
在上一篇文章中,我們已經介紹了Buffer,這篇文章主要介紹剩下兩個元件:Channel和Selector。
Channel翻譯過來是“通道”的意思,所有的Java NIO都要經過Channel。一個Channel物件其實就對應了一個IO連線。Java NIO中主要有以下Channel實現:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
分別用於處理檔案IO、UDP、TCP客戶端、TCP服務端。
這裡以ServerSocketChannel和SocketChannel為例,介紹一些常用的方法。
// server:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8080));
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int readBytes = socketChannel.read(buffer);
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
System.out.println("server 收到:" + body);
}
對於服務端來說,先用open
方法建立一個物件,然後使用bind
方法繫結埠。
繫結以後,使用accept
方法等待新的連線進來,這個方法是阻塞的。一旦有了新的連線,才會解除阻塞。再次呼叫可以阻塞等待下一個連線。
與Buffer配合,使用read
方法可以把資料從Channel讀到Buffer裡面,然後做後續處理。
// Client:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
buffer.put("hi, 這是client".getBytes(StandardCharsets.UTF_8));
buffer.flip();
socketChannel.write(buffer);
對於客戶端來說,有一些微小的區別。客戶端不需要bind
監聽埠,而是直接connect
去嘗試連線服務端。
同樣與Buffer配合,Channel使用write
方法可以把資料從Buffer寫到Channel裡,然後後續就可以做網路傳輸了。
Selector翻譯過來叫做“選擇器”,Selector允許一個執行緒處理多個Channel。Selector的應用場景是:如果你的應用開啟了多個連線(Channel),但每個連線的流量都很低。比如:聊天伺服器或者HTTP伺服器。
使用Selector很簡單。使用open
方法建立一個Selector物件,然後把Channel註冊到Selector上。
// 建立一個Selector
Selector selector = Selector.open();
// 把一個Channel註冊到Selector
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
需要注意的是,一定要使用configureBlocking(false)
把Channel設定成非阻塞模式,否則會丟擲IllegalBlockingModeException
異常。
Channel的register
有兩個過載方法:
SelectionKey register(Selector sel, int ops) {
return register(sel, ops, null);
}
SelectionKey register(Selector sel, int ops, Object att);
對於ops
引數,即selector要關心這個Channel的事件型別,在SelectionKey
類裡面有這樣幾個常量:
- OP_READ 可以從Channel讀資料
- OP_WRITE 可以寫資料到Channel
- OP_CONNECT 連線上了伺服器
- OP_ACCEPT 有新的連線進來了
如果你對不止一種事件感興趣,使用或運算子即可,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
需要注意的是,FileChannel只有阻塞模式,不支援非阻塞模式,所以它是沒有register方法的!
第三個引數att
是attachment
的縮寫,代表可以傳一個“附件”進去。在返回的SelectionKey
物件裡面,可以獲取以下物件:
- channel():獲取Channel
- selector():獲取Selector
- attachment():獲取附件
- attach(obj):更新附件
除此之外,還有一些判斷當前狀態的方法:
- isReadable()
- isWritable()
- isConnectable()
- isAcceptable()
一般來說,我們很少直接使用單個的SelectionKey,而是從Selector裡面輪詢所有的SelectionKey,比如:
while (selector.select() > 0) {
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
// read
} else if(key.isAcceptable()) {
// accept
}
// 其它條件
keyIterator.remove();
}
}
Selector可以返回兩種SelectionKey集合:
- keys():已註冊的鍵的集合
- selectedKeys():已選擇的鍵的集合
並不是所有註冊過的鍵都仍然有效,有些可能已經被cancel()
方法被呼叫過的鍵。所以一般來說,我們輪詢selectedKeys()
方法。
以下是一個完整的Server-Client Demo:
Server:
public class Server {
public static void main(String[] args) {
try (
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
) {
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8080));
serverSocketChannel.configureBlocking(false);
System.out.println("server 啟動...");
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
SocketChannel socketChannel = (SocketChannel) key.channel();
int readBytes = socketChannel.read(buffer);
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes, StandardCharsets.UTF_8);
System.out.println("server 收到:" + body);
}
} else if(key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client:
public class Client {
public static void main(String[] args) {
try (
SocketChannel socketChannel = SocketChannel.open();
) {
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
System.out.println("client 啟動...");
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
buffer.put("hi, 這是client".getBytes(StandardCharsets.UTF_8));
buffer.flip();
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
作者:公眾號_xy的技術圈
連結:www.imooc.com/article/289136
來源:慕課網
本作品採用《CC 協議》,轉載必須註明作者和本文連結