Java NIO之Selector

前面兩篇文章介紹了NIO中的Buffer和Channel,有了以前的基礎,這篇文章來介紹一下另外一個比較重要的概念----Selector。咱們知道系統線程的切換是消耗系統資源的,若是咱們每個鏈接都用一個線程來管理,資源的開銷會很是大,這個時候就能夠用Selector。經過Selector能夠實現一個線程管理多個Channel,以下圖:java

Selector

Selector使用

打開

使用以前得到一個Selector對象bash

Selector selector = Selector.open();
複製代碼

註冊

要把Channel註冊到Selector上,Channel必需是非阻塞的。所以FileChannel是沒法註冊到Selector的。若是註冊的時候不調用configureBlocking方法就會拋出IllegalBlockingModeException異常。微信

SelectionKey

SelectionKey共有四種socket

  • OP_ACCEPT
  • OP_CONNECT
  • OP_WRITE
  • OP_READ

ServerSocketChannel註冊

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
複製代碼

ServerSocketChannel的Operation Set只能是OP_ACCEPT,若是在註冊的時候添加了OP_CONNECT、OP_WRITE或OP_READ會報異常。例如按照如下寫法ui

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT);
複製代碼

就會拋出下面的異常spa

Exception in thread "main" java.lang.IllegalArgumentException
	at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:199)
	at java.nio.channels.SelectableChannel.register(SelectableChannel.java:280)
	at com.nio.sample.selector.SelectorServerSocketChannelSample.main(SelectorServerSocketChannelSample.java:27)
複製代碼

ServerSocketChannel的validOps能夠看到只有OP_ACCEPT是合法的線程

public final int validOps() {
    return SelectionKey.OP_ACCEPT;
}
複製代碼

SocketChannel註冊

socketChannel.register(selector, SelectionKey.OP_CONNECT);
複製代碼

SocketChannel的Operation Set只能是OP_CONNECT、OP_WRITE和OP_READ,若是在註冊的時候添加了OP_ACCEPT一樣會報異常。3d

SocketChannel的validOps能夠看到只有OP_READ、OP_WRITE、OP_CONNECT是合法的rest

public final int validOps() {
    return (SelectionKey.OP_READ
            | SelectionKey.OP_WRITE
            | SelectionKey.OP_CONNECT);
}
複製代碼

註冊成功以後,咱們經過一個demo實現,客戶端和服務端交互:code

服務端:

public static void main(String[] args) throws Exception {

	ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

	serverSocketChannel.socket().bind(new InetSocketAddress(9000));
	serverSocketChannel.configureBlocking(false);

	Selector selector = Selector.open();

	// configureBlocking 若是不設置非阻塞,register的時候會報異常
	// java.nio.channels.IllegalBlockingModeException
	serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

	while (true) {

		int selected = selector.select();
		
		if (selected > 0) {

			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while (iterator.hasNext()) {

				SelectionKey selectionKey = iterator.next();
				iterator.remove();

				if (selectionKey.isAcceptable()) {
					System.err.println("Acceptable");
					SocketChannel socketChannel = serverSocketChannel.accept();
					socketChannel.configureBlocking(false);
					socketChannel.register(selector, SelectionKey.OP_READ);
				} else if (selectionKey.isReadable()) {
					System.err.println("Readable");
					SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
					ByteBuffer buffer = ByteBuffer.allocate(128);
					socketChannel.read(buffer);
					System.out.println("接收來自客戶端的數據:" + new String(buffer.array()));
					selectionKey.interestOps(SelectionKey.OP_WRITE);
				} else if (selectionKey.isWritable()) {
					System.err.println("Writable");
					SocketChannel channel = (SocketChannel) selectionKey.channel();
					String content = "向客戶端發送數據 : " + System.currentTimeMillis();
					ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
					channel.write(buffer);
					selectionKey.interestOps(SelectionKey.OP_READ);
				}
			}
		}
	}
}
複製代碼

咱們來看一下服務端的邏輯

一、服務端註冊到selector,而後interest set(ops)設置爲SelectionKey.OP_ACCEPT等待客戶端鏈接。

二、客戶端鏈接到達,調用到selectionKey.isAcceptable()方法,接收客戶端鏈接,而後得到一個channel,並把

interest set設置爲SelectionKey.OP_READ等待從通道中讀數據。

三、當客戶端發送的數據到達,selectionKey.isReadable() 被觸發,接收客戶端的數據並打印,而後把selectionKey.interestOps 設置爲SelectionKey.OP_WRITE,向客戶端發送數據。

四、當可寫以後selectionKey.isWritable()被觸發,向客戶端發送數據,同時selectionKey.interestOps再次設置爲

SelectionKey.OP_READ等待客戶端數據到達。

客戶端:

public static void main(String[] args) throws IOException {

	SocketChannel socketChannel = SocketChannel.open();

	socketChannel.configureBlocking(false);
	Selector selector = Selector.open();
	socketChannel.register(selector, SelectionKey.OP_CONNECT);

	socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000));

	while (true) {
		int select = selector.select();

		if (select > 0) {

			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while (iterator.hasNext()) {

				SelectionKey selectionKey = iterator.next();

				if (selectionKey.isConnectable()) {
					System.err.println("Connectable");
					SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
					clientChannel.finishConnect();
					selectionKey.interestOps(SelectionKey.OP_WRITE);

				} else if (selectionKey.isReadable()) {
					System.out.println("Readable");
					SocketChannel channel = (SocketChannel) selectionKey.channel();
					ByteBuffer buffer = ByteBuffer.allocate(128);
					channel.read(buffer);
					selectionKey.interestOps(SelectionKey.OP_WRITE);
					System.out.println("收到服務端數據" + new String(buffer.array()));

				} else if (selectionKey.isWritable()) {
					SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
					String str = "qiwoo mobile";
					ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
					clientChannel.write(buffer);
					selectionKey.interestOps(SelectionKey.OP_READ);
					System.out.println("向服務端發送數據" + new String(buffer.array()));
				}

				iterator.remove();
			}
		}
	}
}
複製代碼

再來看一下服務端的邏輯

一、向服務端發起鏈接請求。

二、selectionKey.isConnectable()被觸發,鏈接成功以後,selectionKey.interestOps設置爲SelectionKey.OP_WRITE,準備向服務端發送數據。

三、channel可寫以後selectionKey.isWritable()被觸發,向服務端發送數據,以後selectionKey.interestOps設置爲SelectionKey.OP_READ,等待服務端過來的數據。

四、服務端數據發過來以後,selectionKey.isReadable()被觸發,讀取服務端數據以後selectionKey.interestOps設置爲SelectionKey.OP_WRITE向服務端寫數據。

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索