选择器Selector
傳統的Server/Client 模式會基于TPR(Thread per Request),服務器會為每個客戶端請求建立一個線程,由該線程單獨負責處理一個客戶請求。這種模式帶來的一個問題就是線程數量的劇增,大量的線程會增大服務器的開銷。大多數的實現為了避免這個問題,都采用了線程池模型,并設置線程池線程的最大數量,這又帶來了新的問題,如果線程池中有200 個線程,而有200 個用戶都在進行大文件下載,會導致第201個用戶的請求無法及時處理,即便第201 個用戶只想請求一個幾KB 大小的頁面。傳統的Server/Client模式如下圖所示:
NIO 中非阻塞I/O 采用了基于Reactor 模式的工作方式,I/O 調用不會被阻塞,相反是注冊感興趣的特定I/O 事件,如可讀數據到達,新的套接字連接等等,在發生特定事件時,系統再通知我們。NIO 中實現非阻塞I/O 的核心對象就是Selector,Selector 就是注冊各種I/O 事件地方,而且當那些事件發生時,就是這個對象告訴我們所發生的事件,如下圖所示:
從圖中可以看出,當有讀或寫等任何注冊的事件發生時,可以從Selector 中獲得相應的SelectionKey,同時從SelectionKey 中可以找到發生的事件和該事件所發生的具體的SelectableChannel,以獲得客戶端發送過來的數據。
使用NIO 中非阻塞I/O 編寫服務器處理程序,大體上可以分為下面三個步驟:
1. 向Selector 對象注冊感興趣的事件。
2. 從Selector 中獲取感興趣的事件。
3. 根據不同的事件進行相應的處理。
接下來我們用一個簡單的示例來說明整個過程。首先是向Selector 對象注冊感興趣的事件:
/* * 注冊事件 */ private Selector getSelector() throws IOException {// 創建Selector 對象Selector sel = Selector.open();// 創建可選擇通道,并配置為非阻塞模式ServerSocketChannel server = ServerSocketChannel.open();server.configureBlocking(false);// 綁定通道到指定端口ServerSocket socket = server.socket();InetSocketAddress address = new InetSocketAddress(port);socket.bind(address);// 向Selector 中注冊感興趣的事件server.register(sel, SelectionKey.OP_ACCEPT);return sel; }創建了ServerSocketChannel 對象,并調用configureBlocking()方法,配置為非阻塞模式,接下來的三行代碼把該通道綁定到指定端口,最后向Selector 中注冊事件,此處指定的是參數是OP_ACCEPT,即指定我們想要監聽accept 事件,也就是新的連接發生時所產生的事件,對于ServerSocketChannel 通道來說,我們唯一可以指定的參數就是OP_ACCEPT。
從Selector 中獲取感興趣的事件,即開始監聽,進入內部循環:
/* * 開始監聽 */ public void listen() {System.out.println("listen on " + port);try {while(true) {// 該調用會阻塞,直到至少有一個事件發生selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iter = keys.iterator();while (iter.hasNext()) {SelectionKey key = (SelectionKey) iter.next();iter.remove();process(key);}}} catch (IOException e) {e.printStackTrace();} }在非阻塞I/O 中,內部循環模式基本都是遵循這種方式。首先調用select()方法,該方法會阻塞,直到至少有一個事件發生,然后再使用selectedKeys()方法獲取發生事件的SelectionKey,再使用迭代器進行循環。
最后一步就是根據不同的事件,編寫相應的處理代碼:
/* * 根據不同的事件做處理 */ private void process(SelectionKey key) throws IOException{// 接收請求if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel channel = server.accept();channel.configureBlocking(false);channel.register(selector, SelectionKey.OP_READ);}// 讀信息else if (key.isReadable()) {SocketChannel channel = (SocketChannel) key.channel();int len = channel.read(buffer);if (len > 0) {buffer.flip();content = new String(buffer.array(),0,len);SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE);sKey.attach(content);} else {channel.close();}buffer.clear();}// 寫事件else if (key.isWritable()) {SocketChannel channel = (SocketChannel) key.channel();String content = (String) key.attachment();ByteBuffer block = ByteBuffer.wrap(("輸出內容:" + content).getBytes());if(block != null){channel.write(block);}else{channel.close();}} }此處分別判斷是接受請求、讀數據還是寫事件,分別作不同的處理。在Java1.4 之前的I/O 系統中,提供的都是面向流的I/O系統,系統一次一個字節地處理數據,一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據,面向流的I/O 速度非常慢,而在Java 1.4 中推出了NIO,這是一個面向塊的I/O 系統,系統以塊的方式處理處理,每一個操作在一步中產生或者消費一個數據庫,按塊處理要比按字節處理數據快的多。
?
總結
以上是生活随笔為你收集整理的选择器Selector的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OAauth2.0包括以下角
- 下一篇: 通道Channel