NIO同BIO的區別在於NIO的全部操做均可以是非阻塞的,這裏嘗試將以前用BIO實現的htp服務器改造爲用NIO實現,在改造過程當中碰到很多問題,只能說知易行難html
1 import java.io.IOException; 2 import java.net.InetSocketAddress; 3 import java.nio.ByteBuffer; 4 import java.nio.CharBuffer; 5 import java.nio.channels.SelectionKey; 6 import java.nio.channels.Selector; 7 import java.nio.channels.ServerSocketChannel; 8 import java.nio.channels.SocketChannel; 9 import java.nio.charset.Charset; 10 import java.util.Iterator; 11 12 public class NioServer { 13 private String ip; 14 15 private int port; 16 17 private Selector selector; 18 19 public NioServer(String ip, int port) { 20 this.ip = ip; 21 this.port = port; 22 } 23 24 public void startListen() throws IOException { 25 selector = Selector.open(); 26 ServerSocketChannel serverChannel = ServerSocketChannel.open(); 27 serverChannel.configureBlocking(false); 28 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 29 serverChannel.bind(new InetSocketAddress(ip, port)); 30 31 while (true) { 32 //不能使用select方法,該方法會阻塞,若是在阻塞過程當中channel狀態就緒,會所以處阻塞而沒法執行。 33 //因此,若是調用阻塞方法,下面對channel狀態的處理得另起一個常駐線程 34 int result = selector.selectNow(); 35 if (result == 0) { 36 continue; 37 } 38 39 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 40 while (it.hasNext()) { 41 SelectionKey key = it.next(); 42 if (key.isAcceptable()) { 43 accept(key); 44 } else if (key.isReadable()) { 45 read(key); 46 } else if (key.isWritable()) { 47 write(key); 48 } else { 49 System.out.println("Unknow selector type"); 50 } 51 52 //必定要調用remove方法將已經處理過的SelectionKey清除掉,不然會形成後面的請求沒法接受 53 it.remove(); 54 } 55 } 56 } 57 58 private void accept(SelectionKey key) throws IOException { 59 System.out.println("Receive connection"); 60 ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); 61 SocketChannel channel = serverSocketChannel.accept(); 62 63 if (channel != null) { 64 channel.configureBlocking(false); 65 channel.register(selector, SelectionKey.OP_READ); 66 } 67 System.out.println("Connection end"); 68 } 69 70 private void read(SelectionKey key) throws IOException { 71 System.out.println("Start read"); 72 SocketChannel channel = (SocketChannel) key.channel(); 73 ByteBuffer buffer = ByteBuffer.allocate(64); 74 boolean hasContent = false; 75 76 //這裏的判斷條件不能是不等於-1,由於channel一直都在,只是在數據被讀完后里面爲空,返回的長度是0.用-1判斷會無限循環沒法退出 77 while (channel.read(buffer) > 0) { 78 buffer.flip(); //切換爲讀模式 79 CharBuffer cb = Charset.forName("UTF-8").decode(buffer); 80 System.out.print(cb.toString()); 81 buffer.clear(); 82 hasContent = true; 83 } 84 85 if (hasContent) { 86 //設置interestOps,用於寫響應 87 key.interestOps(SelectionKey.OP_WRITE); 88 } else { 89 channel.close(); 90 } 91 System.out.println("Read end"); 92 } 93 94 private void write(SelectionKey key) throws IOException { 95 System.out.println("Start write"); 96 SocketChannel channel = (SocketChannel) key.channel(); 97 98 String resText = getResponseText(); 99 ByteBuffer buffer = ByteBuffer.wrap(resText.getBytes()); 100 101 //此處不可以使用channel.write(buffer) != -1來判斷,由於在兩端都不關閉的狀況下,會一直返回0,致使該循環沒法退出 102 while (buffer.hasRemaining()) { 103 channel.write(buffer); 104 } 105 channel.close(); 106 System.out.println("End write"); 107 } 108 109 private String getResponseText() { 110 StringBuffer sb = new StringBuffer(); 111 sb.append("HTTP/1.1 200 OK\n"); 112 sb.append("Content-Type: text/html; charset=UTF-8\n"); 113 sb.append("\n"); 114 sb.append("<html>"); 115 sb.append(" <head>"); 116 sb.append(" <title>"); 117 sb.append(" NIO Http Server"); 118 sb.append(" </title>"); 119 sb.append(" </head>"); 120 sb.append(" <body>"); 121 sb.append(" <h1>Hello World!</h1>"); 122 sb.append(" </body>"); 123 sb.append("</html>"); 124 125 return sb.toString(); 126 } 127 128 public static void main(String[] args) { 129 NioServer server = new NioServer("127.0.0.1", 8080); 130 try { 131 server.startListen(); 132 } catch (IOException e) { 133 e.printStackTrace(); 134 } 135 } 136 137 }
這裏是將對請求的接受(accept)也使用Selector處理了,在該場景下,在處理完SelectioKey後必定要remove,不然會致使後面的請求不響應。在jetty源碼及其它示例中,Selector只處理了對請求的讀以及響應的寫,請求接受並無使用selector,這種場景下能夠不remove(僅不出現後續請求不響應的問題,不表明沒有其它問題):java
1 while (true) { 2 SocketChannel channel = serverChannel.accept(); 3 if(channel != null) { 4 channel.configureBlocking(false); 5 channel.register(selector, SelectionKey.OP_READ); 6 } 7 8 //不能使用select方法,該方法會阻塞,若是在阻塞過程當中channel狀態就緒,會所以處阻塞而沒法執行。 9 //因此,若是調用阻塞方法,下面對channel狀態的處理得另起一個常駐線程 10 int result = selector.selectNow(); 11 if (result == 0) { 12 continue; 13 } 14 15 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 16 while (it.hasNext()) { 17 SelectionKey key = it.next(); 18 if (key.isReadable()) { 19 //讀取請求中的數據 20 read(key); 21 } else if (key.isWritable()) { 22 //寫響應數據 23 write(key); 24 } else { 25 System.out.println("Unknow selector type: " + key.readyOps()); 26 } 27 } 28 }