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

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         }
相關文章
相關標籤/搜索