Java Nio之Selector源碼分析

具體過程圖,上面寫出了核心步驟,可對照下面觀看。

在這裏插入圖片描述

Pipe——單向管道

Pipe使用兩組Channel,SinkChannel用於發送數據,SourceChannel用於接收數據。

public class PipeDemo {
    public static void main(String[] args) throws Exception {
        String msg = "Hello world!";
        Pipe pipe = Pipe.open();
        // 用於發送數據的SinkChannel
        Pipe.SinkChannel sinkChannel = pipe.sink();
        sinkChannel.write(ByteBuffer.wrap((msg).getBytes())); // 發送數據
        // 用於接收數據的SourceChannel
        Pipe.SourceChannel sourceChannel = pipe.source();
        ByteBuffer byteBuffer = ByteBuffer.allocate(msg.length());
        sourceChannel.read(byteBuffer);                       // 讀取數據
        System.out.println(new String(byteBuffer.array()));   // 打印數據
    }
}
// 輸出結果:Hello world!

由於Pipe在下面會使用到,所以這裏先介紹一下。

創建好一個Selector

(1)首先,我們會通過Selector.open()創建Selector

Selector selector = Selector.open();

(2)SelectorProvider.provider()最終會創建new WindowsSelectorProvider()對象
openSelector()最終會創建new WindowsSelectorImpl(this)對象
——這兩步跟JDK操作系統版本有關,這裏是Windows。

public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

public class WindowsSelectorProvider extends SelectorProviderImpl {
    public AbstractSelector openSelector() throws IOException {
        return new WindowsSelectorImpl(this);
    }
}

這裏我們已經認識到通過Selector.open()創建的其實是一個WindowsSelectorImpl對象,下面深入其構造函數進行分析。

WindowsSelectorImpl構造函數

WindowsSelectorImpl(SelectorProvider selectorProvider) throws IOException {
	super(selectorProvider);
	// 省略,下面講解
}

(1)先來看看super()中的方法
在此之前,先來看看Selector的關係圖
在這裏插入圖片描述
SelectorImpl會爲我們初始化publicKets和publicSelectedKeys

public abstract class SelectorImpl extends AbstractSelector {
	/** 已選擇集合,select()添加到該鍵集並返回 */
	protected Set<SelectionKey> selectedKeys = new HashSet();
	/** 鍵集,register()時添加到該鍵級 */
	protected HashSet<SelectionKey> keys = new HashSet();
    protected SelectorImpl(SelectorProvider selectorProvider) {
        super(selectorProvider);
        // 省略
    }
}

AbstractSelector會初始化provider,這裏是WindowsSelectorProvider

public abstract class AbstractSelector extends Selector {
	private final SelectorProvider provider;
	/** 已取消鍵集,已被取消但其通道尚未註銷的鍵的集合 */
	private final Set<SelectionKey> cancelledKeys = new HashSet<SelectionKey>();
    protected AbstractSelector(SelectorProvider provider) {
        this.provider = provider;
    }
}

這裏不需要清晰的明白上面各變量的具體含義,只需記得有下面這三種即可:

  • keys:鍵集
  • selectedKeys:已選擇鍵集
  • cancelledKeys:已取消鍵集

(2)看完了父類的構造方法,接下來看WindowsSelectorImpl的構造函數和局部變量。

在介紹構造函數之前,先來介紹兩個佈局變量,上面介紹了Pipe的使用。並瞭解了Pipe使用兩組Channel,SinkChannel用於發送數據,SourceChannel用於接收數據。這裏介紹Pipe是如何創建的。

private final Pipe wakeupPipe = Pipe.open();

其核心在PipeImpl內部類LoopbackConnector的run()方法中

private class LoopbackConnector implements Runnable {
	private LoopbackConnector() {
	}

	public void run() {
		ServerSocketChannel serverSocketChannel = null;
		SocketChannel socketChannel1 = null;
		SocketChannel socketChannel2 = null;

		try {
			ByteBuffer byteBuffer1 = ByteBuffer.allocate(16);
			ByteBuffer byteBuffer2 = ByteBuffer.allocate(16);
			inetAddressetAddress inetAddress = InetAddress.getByName("127.0.0.1");
			assert inetAddress.isLoopbackAddress();
			InetSocketAddress inetSocketAddress = null;
			while(true) {
				// ServerSocketChannel綁定端口0
				if (serverSocketChannel == null || !serverSocketChannel.isOpen()) {
					serverSocketChannel = ServerSocketChannel.open();
					serverSocketChannel.socket().bind(new InetSocketAddress(inetAddress, 0)); 
					inetSocketAddress = new InetSocketAddress(inetAddress, serverSocketChannel.socket().getLocalPort());
				}
				// 創建socketChnnel1,用於發送數據
				socketChannel1 = SocketChannel.open();
				// 生成隨機字節寫到byteBuffer1
				PipeImpl.RANDOM_NUMBER_GENERATOR.nextBytes(byteBuffer1.array());
				do {
					socketChannel1.write(byteBuffer1); // 將byteBuffer1數據寫到socketChannel1
				} while(byteBuffer1.hasRemaining());
				byteBuffer1.rewind();
				// serverSocketChannel調用accept()接收一個SocketChannel,用於讀取數據
				socketChannel2 = serverSocketChannel.accept();
				do {
					socketChannel2.read(byteBuffer2); // socketChannel2將數據寫道byteBuffer2
				} while(byteBuffer2.hasRemaining());
				byteBuffer2.rewind();
				// 判斷是否能正常地讀寫數據
				if (byteBuffer2.equals(byteBuffer1)) { 
					PipeImpl.this.source = new SourceChannelImpl(Initializer.this.sp, socketChannel1);
					PipeImpl.this.sink = new SinkChannelImpl(Initializer.this.sp, socketChannel2);
					break;
				}
				// 不能正確讀寫數據則關閉SocketChannel資源
				socketChannel2.close();
				socketChannel1.close();
			}
		} catch (IOException e) {
			// 關閉SocketChannel
		} finally {
			// 關閉ServerSocketChannel
		}
	}
}

在這裏插入圖片描述
如果發送的數據與接收的數據內容相同,則將發送的SocketChannel作爲source(SourceChannelImpl),使用接收的SocketChannel作爲sink(SinkChannelImpl)。

如果看源碼,SourceChannelImpl繼承了SourceChannel,而SourceChannel實現了ReadableByteChannel, ScatteringByteChannel兩個接口,而這兩個接口只提供了只讀的方法,因此SourceChannel只能接受數據。

public static abstract class SourceChannel
    extends AbstractSelectableChannel
    implements ReadableByteChannel, ScatteringByteChannel

SinkChannelImpl繼承了SinkChannel,同樣只能寫數據。

public static abstract class SinkChannel
    extends AbstractSelectableChannel
    implements WritableByteChannel, GatheringByteChannel

wakeuppipe是我們介紹的第一個WindowsSelectorImpl局部變量,第二個局部變量是pollWrapper,用於存儲socket句柄fd以及事件events。

private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);

這裏先看一下構造函數,具體的方法使用到再看。

class PollArrayWrapper {
	// 存儲和獲取操作
	private AllocatedNativeObject pollArray;
    PollArrayWrapper(int var1) {
        int var2 = var1 * SIZE_POLLFD;
		// 初始化時調用unsafe.allocateMemory()申請一塊內存
        this.pollArray = new AllocatedNativeObject(var2, true);
		// 用pollArrayAddress記錄內存地址
        this.pollArrayAddress = this.pollArray.address();
        this.size = var1;
    }
}

現在我們回過來看WindowsSelectorImpl的構造函數

WindowsSelectorImpl(SelectorProvider selectorProvider) throws IOException {
	super(selectorProvider);
	// 這裏的wakeupPipe就是上面介紹的局部變量PipeImpl
	// 獲取保存SourceChannel的socket句柄
	this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
	// 獲取SinkChannel
	SinkChannelImpl sinkChannelImpl = (SinkChannelImpl)this.wakeupPipe.sink();
	sinkChannelImpl.sc.socket().setTcpNoDelay(true);
	// 獲取保存SinkChannel的socket句柄
	this.wakeupSinkFd = sinkChannelImpl.getFDVal();
	this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
}

addWakeupSocket:當事件觸發,會通知對應的socket

class PollArrayWrapper {
    void addWakeupSocket(int var1, int var2) { // (SourceChannel的socket句柄, 0)
    	// 添加描述符
        this.putDescriptor(var2, var1); // (0, SourceChannel的socket句柄)
        // 添加事件, Net.POLLIN表示有數據可讀
        this.putEventOps(var2, Net.POLLIN); // (0, Net.POLLIN)
    }
    void putDescriptor(int var1, int var2) {
        this.pollArray.putInt(SIZE_POLLFD * var1 + 0, var2); // (0, socket句柄)
    }
    void putEventOps(int var1, int var2) {
        this.pollArray.putShort(SIZE_POLLFD * var1 + 4, (short)var2); // (4, socket句柄)
    }
}

通過上面代碼我們也可以看出socket句柄佔用4個字節,事件佔用兩個字節。

——到這裏我們已經清晰地瞭解創建一個Selector的具體流程。包括常用的三個鍵集keys、selectedKeys、cancelledKeys,以及Pipe管道和PollArrayWrapper。下面將介紹Selector的常用方法。

WindowsSelectorImpl常用方法

(1)register()
SelectorImpl

protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object att) {
    if (!(var1 instanceof SelChImpl)) {
        throw new IllegalSelectorException();
    } else {
    	// 創建一個註冊標識
        SelectionKeyImpl selectionKey = new SelectionKeyImpl((SelChImpl)ch, this);
        // 爲selectionKey附加額外屬性
        selectionKey.attach(att);
        synchronized(this.publicKeys) {
        	// 添加socket句柄
            this.implRegister(selectionKey);
        }
        // 添加事件
        selectionKey.interestOps(ops);
        return selectionKey;
    }
}

WindowsSelectorImpl
用到的局部變量

/** 用於存儲註冊標識 */
private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8];
/** 用於存儲Socket句柄和events */
private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);
/** 可以認爲存儲的是<socket句柄,SelectionKey> */
private final WindowsSelectorImpl.FdMap fdMap = new WindowsSelectorImpl.FdMap();
/** 已註冊的Channel總數,從1開始 */
private int totalChannels = 1;

添加socket句柄

protected void implRegister(SelectionKeyImpl selectionKey) {
    synchronized(this.closeLock) {
        if (this.pollWrapper == null) {
            throw new ClosedSelectorException();
        } else {
        	// 功能(1)判斷是否需要擴容,如果需要則對channelArray、pollWrapper進行擴容
        	// 功能(2)每增加1024個Channel則增加一個線程
            this.growIfNeeded();
            // 添加selectionKey到channelArray
            this.channelArray[this.totalChannels] = selectionKey;
            // 設置selectionKey的index屬性,添加事件時會用到
            selectionKey.setIndex(this.totalChannels);
            this.fdMap.put(selectionKey);
            // 添加到已註冊鍵集
            this.keys.add(selectionKey);
            // 添加socket句柄到pollWrapper
            this.pollWrapper.addEntry(this.totalChannels, selectionKey);
            // 將已註冊的channel總數 + 1
            ++this.totalChannels;
        }
    }
}
void addEntry(int var1, SelectionKeyImpl var2) { // (1, selectionKey) // 下一次爲(2, sk)
	this.putDescriptor(var1, var2.channel.getFDVal()); // (1, socket句柄) // 下一次爲(2, s)
}

void putDescriptor(int var1, int var2) { // (1, socket句柄) // 下一次爲(2, s)
	this.pollArray.putInt(SIZE_POLLFD * var1 + 0, var2); //(8, socket句柄)//下一次爲(16, s)
}

selectionKey.interestOps(ops)——添加事件

public SelectionKey interestOps(int ops) {
	this.ensureValid();
	return this.nioInterestOps(ops);
}

public SelectionKey nioInterestOps(int ops) {
	if ((ops & ~this.channel().validOps()) != 0) {
		throw new IllegalArgumentException();
	} else {
		// 添加事件到pollWrapper
		this.channel.translateAndSetInterestOps(ops, this);
		this.interestOps = ops;
		return this;
	}
}
// translateAndSetInterestOps在不同的類實現不同這裏主要看ServerSocketChannel和SocketChannel
// SocketChannel
public void translateAndSetInterestOps(int ops, SelectionKeyImpl selectionKey) {
	int finalOps = 0;
	if ((ops & 1) != 0) { // 1 :讀(read)
		finalOps |= Net.POLLIN;
	}
	if ((ops & 4) != 0) { // 2 : 寫(write)
		finalOps |= Net.POLLOUT;
	}
	if ((ops & 8) != 0) { // 8 : 連接(connect)
		finalOps |= Net.POLLCONN;
	}
	selectionKey.selector.putEventOps(selectionKey, finalOps);
}
// ServerSocketChannel
public void translateAndSetInterestOps(int ops, SelectionKeyImpl selectionKey) {
	int finalOps = 0;
	if ((ops & 16) != 0) { // 16 :接收(accept)
		finalOps |= Net.POLLIN;
	}
	selectionKey.selector.putEventOps(selectionKey, finalOps);
}

public void putEventOps(SelectionKeyImpl selectionKey, int ops) {
	synchronized(this.closeLock) {
		if (this.pollWrapper == null) {
			throw new ClosedSelectorException();
		} else {
			int index = selectionKey.getIndex();
			if (index == -1) { // index是大於0的
				throw new CancelledKeyException();
			} else {
				this.pollWrapper.putEventOps(index, ops); // 添加事件(具體看上面)
			}
		}
	}
}

(2)select()

// Selector
public int select() throws IOException {
	return this.select(0L);
}
// SelectorImpl
public int select(long timeout) throws IOException {
	if (timeout < 0L) {
		throw new IllegalArgumentException("Negative timeout");
	} else {
		return this.lockAndDoSelect(timeout == 0L ? -1L : timeout);
	}
}
// SelectorImpl
private int lockAndDoSelect(long timeout) throws IOException {
	// 這裏省略了沒有意義的
	return this.doSelect(timeout);
}

下面是核心方法,這裏只對關鍵代碼進行分析

// WindowsSelectorImpl
protected int doSelect(long timeout) throws IOException {
	if (this.channelArray == null) {
		throw new ClosedSelectorException();
	} else {
		this.timeout = timeout;
		// 檢查cancelledKeys,清除裏面的selectionKey與channel、selector的聯繫
		this.processDeregisterQueue();
		if (this.interruptTriggered) {
			this.resetWakeupSocket();
			return 0;
		} else {
			// 調整線程數量,少了就添加,多了就移除
			// 移除是通過修改線程內的volatile boolean zombie標識
			this.adjustThreadsCount();
			// 調整要執行的線程數
			this.finishLock.reset();
			// 喚醒所有SelectThread線程,所有線程在檢測到一個就緒socket句柄後返回
			this.startLock.startThreads();
			try {
				this.begin();
				try {
					// 當前線程監聽0-1024個socket句柄,其他線程是從1025開始
					this.subSelector.poll();
				} catch (IOException e) {
					this.finishLock.setException(e);
				}
				// waitForHelperThreads()用於阻塞當前線程直到所有線程執行完畢
				// 每個線程執行完畢調用finishLock.threadFinished(),所有線程執行完畢會在該方法調用notify()
				if (this.threads.size() > 0) {
					this.finishLock.waitForHelperThreads();
				}
			} finally {
				this.end();
			}
			this.finishLock.checkForException();
			// 再次檢查cancelledKeys
			this.processDeregisterQueue();
			// 更新selectedKeys
			int var3 = this.updateSelectedKeys();
			this.resetWakeupSocket();
			return var3;
		}
	}
}

檢查cancelledKeys具體代碼

// 省略synchronized、及try-catch等
void processDeregisterQueue() throws IOException {
	Set cancelledKeys = this.cancelledKeys();
	Iterator iterator = cancelledKeys.iterator();
	while(iterator.hasNext()) {
		SelectionKeyImpl selectionKey = (SelectionKeyImpl)iterator.next();
		// 清理Channel、Selector與該SelectionKey的聯繫
		this.implDereg(selectionKey);
		iterator.remove();
	}
}
protected void implDereg(SelectionKeyImpl selectionKey) throws IOException {
	int index = selectionKey.getIndex();
	assert index >= 0;
	synchronized(this.closeLock) {
		if (index != this.totalChannels - 1) {
			// 獲取最後一個註冊標識
			SelectionKeyImpl lastSelectionKey = this.channelArray[this.totalChannels - 1];
			// 替換掉channelArray index位置的註冊標識
			this.channelArray[index] = lastSelectionKey;
			lastSelectionKey.setIndex(index);
			// 獲取最後一個註冊標識的socket句柄和事件、替換到index位置
			this.pollWrapper.replaceEntry(this.pollWrapper, this.totalChannels - 1, this.pollWrapper, index);
		}
		selectionKey.setIndex(-1);
	}
	// 下面是一些後置的清理工作
	this.channelArray[this.totalChannels - 1] = null;
	--this.totalChannels;
	if (this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
		--this.totalChannels;
		--this.threadsCount;
	}
	this.fdMap.remove(selectionKey);
	this.keys.remove(selectionKey);
	this.selectedKeys.remove(selectionKey);
	// 清除channel中的註冊標識
	this.deregister(selectionKey);
	SelectableChannel selectableChannel = selectionKey.channel();
	if (!selectableChannel.isOpen() && !selectableChannel.isRegistered()) {
		((SelChImpl)selectableChannel).kill();
	}
}

執行真正的select具體代碼跟三個WindowsSelectorImpl的內部類有關
StartLock

private final class StartLock {
	/** 執行select()方法的次數 */
	private long runsCounter;
	private StartLock() {}
	/** 喚醒所有阻塞在該鎖上的線程 */
	private synchronized void startThreads() {
		++this.runsCounter;
		this.notifyAll();
	}
	private synchronized boolean waitForStart(WindowsSelectorImpl.SelectThread selectThread) {
		// 合法判斷,不合法阻塞線程
		while(this.runsCounter == selectThread.lastRun) {
			try {
				WindowsSelectorImpl.this.startLock.wait();
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}
		// 如果是殭屍線程,返回true,之後線程會跳出for循環
		// 移除線程就是通過這種方式
		if (selectThread.isZombie()) {
			return true;
		} else {
			// 修改線程最後一次執行次數
			selectThread.lastRun = this.runsCounter;
			return false;
		}
	}
}

FinishLock

private final class FinishLock {
	/** 剩餘多少線程還沒執行完本次select */
	private int threadsToFinish;

	private synchronized void threadFinished() {
		// 如果要執行的線程數與總的線程數相等
		if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
			// 調用sink發送數據喚醒阻塞在poll上的selector線程
			WindowsSelectorImpl.this.wakeup();
		}
		--this.threadsToFinish;
		// 如果本輪select執行完畢,是否阻塞在該鎖上的線程
		if (this.threadsToFinish == 0) {
			this.notify();
		}
	}

	private synchronized void waitForHelperThreads() {
		// 如果要執行的線程數與總的線程數相等
		if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
			WindowsSelectorImpl.this.wakeup();
		}
		// 如果其他線程該沒執行完,阻塞當前線程
		while(this.threadsToFinish != 0) {
			try {
				WindowsSelectorImpl.this.finishLock.wait();
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}
	}
}

// 調用sink發送數據喚醒阻塞在poll上的selector線程
public Selector wakeup() {
	synchronized(this.interruptLock) {
		if (!this.interruptTriggered) {
			this.setWakeupSocket();
			this.interruptTriggered = true;
		}
		return this;
	}
}

SelectThread

private final class SelectThread extends Thread {
	private final int index;
	/** 真正執行select操作的執行器 */
	final WindowsSelectorImpl.SubSelector subSelector;
	private long lastRun;
	/** 爲true會跳出for循環 */
	private volatile boolean zombie;

	private SelectThread(int index) {
		this.lastRun = 0L;
		this.index = index;
		this.subSelector = WindowsSelectorImpl.this.new SubSelector(index);
		this.lastRun = WindowsSelectorImpl.this.startLock.runsCounter;
	}

	void makeZombie() {this.zombie = true;}
	boolean isZombie() {return this.zombie;}

	public void run() {
		// waitForStart()會讓殭屍線程跳出for循環
		// threadFinished()會在檢測到就緒事件之後讓其他線程poll()返回
		for(; !WindowsSelectorImpl.this.startLock.waitForStart(this); WindowsSelectorImpl.this.finishLock.threadFinished()) {
			try {
				// 應用操作系統監聽pollWrapper有沒有就緒的socket句柄(阻塞)
				this.subSelector.poll(this.index);
			} catch (IOException e) {
				WindowsSelectorImpl.this.finishLock.setException(e);
			}
		}
	}
}

private final class SubSelector {
	// poll的索引開始處,pollWrapper
    private final int pollArrayIndex;
    // 存放可讀事件fd
    private final int[] readFds;
    // 存放可寫事件fd
    private final int[] writeFds;
    // 存放異常事件fd
    private final int[] exceptFds;
    // ... 
}

selectNow()與select()區別就是最後的timeout,selectNow()的timeout爲0,select()的timeout爲-1,該timeout在SubSelector的poll()方法使用,區別就是阻塞和非阻塞。

完畢,以上如果有誤,歡迎指正。部分涉及到native方法並沒有深入分析,因此有的地方沒有解釋清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章