介紹
Selector一般稱爲選擇器。它是Java NIO核心組件之一,選擇器管理着一個被註冊的通道集合的信息和它們的就緒狀態。通道是和選擇器一起被註冊的,並且使用選擇器來更新通道的就緒狀態。
可選擇通道(SelectableChannel)
抽象類SelectableChannel提供了實現通道的可選擇性所需要的公共方法。繼承該SelectableChannel的通道(如:socket通道)都是可選擇的,因此可以配置成非阻塞模式。
//可以註冊的Channel的抽象
public abstract class SelectableChannel
extends AbstractInterruptibleChannel
implements Channel
{
//返回創建此通道的Provider
public abstract SelectorProvider provider();
//返回註冊的SelectionKey(即事件的操作類型)
public abstract int validOps();
//判斷當前Channel是否有註冊到任意的Selector
public abstract boolean isRegistered();
//獲取該Channel註冊在Selector的SelectionKey(事件類型)
public abstract SelectionKey keyFor(Selector sel);
//將Channel註冊到給定的Selector sel上,並指定註冊的事件類型(SelectionKey.OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT等)
//attachment 爲附屬的resulting key,可以爲null
//只有非阻塞模型的Channel才能調用register方法
public abstract SelectionKey register(Selector sel, int ops, Object att)
//將Channel註冊到給定的Selector sel上,並指定註冊的事件類型(SelectionKey.OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT等)
public final SelectionKey register(Selector sel, int ops)
//配置是否是阻塞模式
public abstract SelectableChannel configureBlocking(boolean block)
//獲取當前是否是阻塞模式
public abstract boolean isBlocking();
//獲取 configureBlocking和register方法同步的鎖
public abstract Object blockingLock();
}
選擇鍵(SelectionKey)
選擇鍵封裝了特定的通道與特定的選擇器的註冊關係。選擇鍵對象被SelectableChannel.register()返回並提供一個表示這種註冊關係的標記。
SelectionKey常用的API如下
//選擇鍵封裝了特定的通道與特定的選擇器的註冊關係
public abstract class SelectionKey {
//返回創建該Key的Channel對象,即使當前key被取消也會返回
public abstract SelectableChannel channel();
//返回創建該key的Selector
public abstract Selector selector();
//判斷該key是否可用
public abstract boolean isValid();
//請求取消此鍵的通道向其選擇器的註冊。返回時,該密鑰將無效,並將被添加到其選擇器的已取消密鑰集中。在下一次選擇操作期間,該鍵將從所有選擇器的鍵集中刪除。
public abstract void cancel();
// 獲取此鍵的 interest 集合。
public abstract int interestOps();
//將此鍵的 interest 集合設置爲給定值。
public abstract SelectionKey interestOps(int ops);
//獲取該通道已經就緒的操作
public abstract int readyOps();
public static final int OP_READ = 1 << 0; //讀操作事件
public static final int OP_WRITE = 1 << 2; //寫操作事件
public static final int OP_CONNECT = 1 << 3; //連接事件
public static final int OP_ACCEPT = 1 << 4; //接受事件
//測試此鍵的通道是否已準備好進行讀取。
public final boolean isReadable()
//測試此鍵的通道是否已準備好進行寫入。
public final boolean isWritable()
// 測試此鍵的通道是否已完成其套接字連接操作。
public final boolean isConnectable()
// 測試此鍵的通道是否已準備好接受新的套接字連接。
public final boolean isAcceptable()
//將附加對象綁定到SelectionKey上,便於識別給定的通道
public final Object attach(Object ob)
//取出綁定在SelectionKey上的附加對象
public final Object attachment()
}
jdk1.8版本SelectionKey有4中事件,OP_READ(讀事件)、OP_WRITE(寫事件)、OP_CONNECT(連接事件)和OP_ACCEPT(接受事件)。
Selector選擇器
Selector常用的API
public abstract class Selector implements Closeable {
//創建一個選擇器
public static Selector open()
//獲取當前選擇器是否已打開
public abstract boolean isOpen()
//返回創建此通道的提供者
public abstract SelectorProvider provider()
//返回此選擇器的鍵集
public abstract Set<SelectionKey> keys()
//返回此選擇器已選擇的鍵
public abstract Set<SelectionKey> selectedKeys()
//獲取已經I/O準備就緒的鍵集,不會阻塞
public abstract int selectNow()
//阻塞直到註冊到Selector上的Channel有事件發生,或者到了超時時間
//指定最長等待阻塞的時間,單位ms
public abstract int select(long timeout)
//阻塞直到註冊在Selector中的Channel 發送可讀寫事件(或其他註冊事件)
public abstract int select()
//喚醒調用select()阻塞的線程
public abstract Selector wakeup()
//關閉Selector
public abstract void close()
}
open方法
open()方法用於創建一個Selector對象
Selector selector = Selector.open();
將Channel註冊到Selector中
我們需要將 Channel 註冊到Selector 中,這樣才能通過 Selector 監控 Channel 中的事件,注:一個Channel要註冊到Selector中,需要先將這個Channel設置成非阻塞模式。
//設置成非阻塞模式
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
select方法
select()和select(long timeout),select方法是Selector的核心方法,這兩個方法會阻塞線程。
select(long timeout):阻塞直到註冊到Selector上的Channel有事件發生,或者到了超時時間指定最長等待阻塞的時間,單位ms
select():阻塞直到註冊在Selector中的Channel 發送可讀寫事件(或其他註冊事件)
除了正常收到事件或超時還有三種方法可以喚醒在select()中阻塞的線程
1. wakeup()
2. close()
3. interrupt()
獲取就緒的Channel
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
//可能有多個註冊事件就緒
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
}
if (key.isConnectable()) {
// a connection was established with a remote server.
}
if (key.isReadable()) {
// a channel is ready for reading
}
if (key.isWritable()) {
// a channel is ready for writing
}
}
注意, 在每次迭代時, 我們都調用 “keyIterator.remove()” 將這個 key 從迭代器中刪除, 因爲 select() 方法僅僅是簡單地將就緒的 IO 操作放到 selectedKeys 集合中, 因此如果我們從 selectedKeys 獲取到一個 key, 但是沒有將它刪除, 那麼下一次 select 時, 這個 key 所對應的 IO 事件還在 selectedKeys 中.
attach 和 attachment 方法
public final Object attach(Object ob) 將附加對象綁定到SelectionKey上,便於識別給定的通道
public final Object attachment() 取出綁定在SelectionKey上的附加對象
ServerSocketChannel和Selector使用示例:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class SelectSocketsThreadPool {
private static final int MAX_THREADS = 5; //線程池數量
public static int PORT_NUMBER = 7786; //指定默認端口號
private ThreadPool pool = new ThreadPool(MAX_THREADS);
public static void main(String[] args) throws Exception {
new SelectSocketsThreadPool().go(args);
}
public void go(String [] args) throws Exception {
int port = PORT_NUMBER;
if (args.length > 0){
port = Integer.parseInt(args[0]);
}
System.out.println("Listening on port "+port);
//初始化ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//該ServerSocketChannel對應的ServerSocket
ServerSocket serverSocket = serverChannel.socket();
//初始化一個Selector
Selector selector = Selector.open();
serverSocket.bind(new InetSocketAddress(port)); //綁定端口
serverChannel.configureBlocking(false); //非阻塞
//綁定 ACCEPT事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//阻塞直到有新的連接
int n= selector.select();
if (n == 0){
continue;
}
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey key = (SelectionKey)it.next();
if (key.isAcceptable()) { //key是ACCEPT
System.out.println("accept connection!");
//獲取該key對應的Channel
System.out.println();
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//接收到的SocketChannel
SocketChannel channel = server.accept();
registerChannel(selector,channel,SelectionKey.OP_READ);
sayHello(channel);
}
if (key.isReadable()){
readDataFromSocket(key);
}
it.remove();
}
}
}
private void sayHello(SocketChannel channel) throws IOException {
buffer.clear();
buffer.put("Hi there!\r\n".getBytes());
buffer.flip();
channel.write(buffer);
}
private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
//連接上來的SocketChannel,註冊到當前Selector上,註冊的操作爲READ
protected void registerChannel(Selector selector, SocketChannel channel, int opRead) throws IOException {
if (channel == null){
return;
}
channel.configureBlocking(false);
channel.register(selector,opRead);
}
//讀取數據
protected void readDataFromSocket(SelectionKey key) {
WorkThread workThread = pool.getWorker();
if (workThread == null){
return;
}
workThread.serviceChannel(key);
}
class ThreadPool {
List idle = new LinkedList();
//創建指定數量的線程並添加到空閒隊列idle中
ThreadPool(int poolSize) {
for (int i = 0 ; i < poolSize; i++){
WorkThread thread = new WorkThread(this);
thread.setName("Worker" + (i+1));
thread.start();
idle.add(thread);
}
}
//從隊列中獲取空閒線程
WorkThread getWorker(){
WorkThread workThread = null;
synchronized (idle){
if (idle.size() > 0){
workThread = (WorkThread)idle.remove(0);
}
}
return workThread;
}
//將線程返還到空閒線程
void returnWorker(WorkThread workThread){
synchronized (idle){
idle.add(workThread);
}
}
}
//線程執行的任務類
class WorkThread extends Thread {
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private ThreadPool pool;
private SelectionKey key;
WorkThread(ThreadPool pool){
this.pool = pool;
}
@Override
public synchronized void run() {
System.out.println(this.getName() +" is ready");
while (true){
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (key == null){
continue;
}
System.out.println(this.getName() +" has been awakened");
try{
drainChannel(key); //處理就緒的key
}catch (Exception e){
System.out.println(" caught "+ e + "closing channel");
try {
key.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
key = null;
this.pool.returnWorker(this); //線程處理完任務放回空閒隊列
}
}
synchronized void serviceChannel(SelectionKey key){
this.key = key;
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
this.notify();
}
//處理就緒的key
private void drainChannel(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
int count;
buffer.clear();
while ((count = channel.read(buffer)) > 0 ){
System.out.println(new String(buffer.array(),0,count));
buffer.flip();
while (buffer.hasRemaining()){
channel.write(buffer);
}
buffer.clear();
}
if (count < 0){
channel.close();
return;
}
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
key.selector().wakeup();
}
private String decode(ByteBuffer bb) {
Charset charset = Charset.forName("ASCII");
return charset.decode(bb).toString();
}
}
}
感謝:
《Java NIO》
https://blog.csdn.net/u014634338/article/details/82865622
http://tutorials.jenkov.com/java-nio/selectors.html