1. 程式人生 > >使用NIO建立簡單的http服務器

使用NIO建立簡單的http服務器

resp 調用 class select 模式 world pri ear tostring

NIO同BIO的區別在於NIO的所有操作都可以是非阻塞的,這裏嘗試將之前用BIO實現的htp服務器改造為用NIO實現,在改造過程中碰到不少問題,只能說知易行難

  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(僅不出現後續請求不響應的問題,不代表沒有其它問題):

 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         }

使用NIO建立簡單的http服務器