使用NIO建立简单的http服务器
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 }