NIO 源碼分析(01) NIO 最簡用法java
Netty 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)linux
Java NIO 主要由三個部分組成:Channel、Buffer 和 Selector。在分析源碼前最好對 NIO 的基本用法和 Linux NIO 在一個基本的瞭解。服務器
本文會提供一個 NIO 最簡使用示例,以後的源碼分析都會基於該示例及其擴展進行。socket
public class Server implements Runnable { public static void main(String[] args) { new Thread(new Server(8765)).start(); } // 1 多路複用器(管理全部的通道) private Selector selector; // 2 創建緩衝區 private ByteBuffer readBuf = ByteBuffer.allocate(1024); private ByteBuffer writeBuf = ByteBuffer.allocate(1024); public Server(int port) { try { //1. 獲取多路複用器 this.selector = Selector.open(); //2. 獲取服務器端通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //3. 切換成非阻塞模式 ssChannel.configureBlocking(false); //4. 綁定端口號 ssChannel.bind(new InetSocketAddress(port)); //5. 把服務器通道註冊到多路複用器上,而且監聽阻塞事件 ssChannel.register(this.selector, SelectionKey.OP_ACCEPT); System.out.println("Server start, port :" + port); } catch (IOException e) { e.printStackTrace(); } } public void run() { while (true) { try { // 1. 阻塞到至少有一個通道在你註冊的事件上就緒,返回的 int 值表示有多少通道已經就緒 int wait = selector.select(); if (wait == 0) continue; //2. 遍歷多路複用器上已經準備就緒的通道 Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isValid()) { if (key.isAcceptable()) { // 3.1 若是爲 accept 狀態,就獲取客戶端通道並註冊到selector上 this.accept(key); } else if (key.isReadable()) { // 3.2 若是爲可讀狀態 this.read(key); } else if (key.isWritable()) { // 3.3 寫數據 this.write(key); } } // 4. 注意必須在處理完通道時本身移除 iterator.remove(); } } catch (IOException e) { e.printStackTrace(); } } } private void write(SelectionKey key) { //ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //ssc.register(this.seletor, SelectionKey.OP_WRITE); } private void read(SelectionKey key) { try { //1. 清空緩衝區舊的數據 this.readBuf.clear(); //2. 獲取以前註冊的socket通道對象 SocketChannel sChannel = (SocketChannel) key.channel(); //3. 讀數據 int len = sChannel.read(this.readBuf); if (len == -1) { key.channel().close(); key.cancel(); return; } //5 有數據則進行讀取 讀取以前要flip()復位 this.readBuf.flip(); byte[] bytes = new byte[this.readBuf.remaining()]; this.readBuf.get(bytes); System.out.println("Server : " + new String(bytes).trim()); //6. 能夠寫回給客戶端數據 } catch (IOException e) { e.printStackTrace(); } } private void accept(SelectionKey key) { try { //1. 獲取服務通道ServerSocketChannel ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel(); //2. 獲取客戶端通道SocketChannel SocketChannel sChannel = ssChannel.accept(); //3. 客戶端通道SocketChannel也要設置成非阻塞模式 sChannel.configureBlocking(false); //4 註冊到多路複用器上,並設置讀取標識 sChannel.register(this.selector, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws IOException { SocketChannel sChannel = null; try { //1. 獲取通道 sChannel = SocketChannel.open( new InetSocketAddress("127.0.0.1", 8765)); //2. 切換成非阻塞模式 sChannel.configureBlocking(false); //3. 分配緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); //4. 把從控制檯讀到數據發送給服務器端 Scanner scanner = new Scanner(System.in); while (true) { String str = scanner.next(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); buf.put((dateFormat.format(new Date()) + ":" + str).getBytes()); buf.flip();// 非阻塞模式下,write()方法在還沒有寫出任何內容時可能就返回了 while (buf.hasRemaining()) { sChannel.write(buf); } buf.clear(); } } finally { sChannel.close(); } }
ok,完成!!!源碼分析
天天用心記錄一點點。內容也許不重要,但習慣很重要!this