引言 在分析Tomcat的请求处理过程之前,得先复习一下Java NIO的一些知识:
概念 NIO最大的特点是面向缓冲区,有三大核心:Channel、Buffer、Selector。
Channel Channel是双向的,既可以读数据,也可以写数据。这里只关注TCP(Server和Client)相关的ServerSocketChannel和SocketChannel。
Buffer 数据总是从通道读取到缓冲区,或者从缓冲区写入通道中。
Selector 用于监听多个通道的事件,比如连接打开、数据到达。
TCP客户端服务端例子 ####ServerHandler与其实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 import java.io.IOException;import java.nio.channels.SelectionKey;public interface ServerHandler { void handleAccept (SelectionKey selectionKey) throws IOException; String handleRead (SelectionKey selectionKey) throws IOException; } import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.StandardCharsets;public class ServerHandlerImpl implements ServerHandler { private int bufferSize = 1024 ; @Override public void handleAccept (SelectionKey selectionKey) throws IOException { SocketChannel socketChannel = ((ServerSocketChannel) selectionKey.channel()).accept(); socketChannel.configureBlocking(false ); socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); System.out.println("请求连接......" ); } @Override public String handleRead (SelectionKey selectionKey) throws IOException { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = (ByteBuffer) selectionKey.attachment(); String receivedStr = "" ; if (socketChannel.read(buffer) == -1 ) { socketChannel.shutdownOutput(); socketChannel.shutdownInput(); socketChannel.close(); System.out.println("连接断开......" ); } else { buffer.flip(); receivedStr = StandardCharsets.UTF_8.newDecoder().decode(buffer).toString(); buffer.clear(); buffer = buffer.put(("服务端接收到的消息为: " + receivedStr).getBytes(StandardCharsets.UTF_8)); buffer.flip(); socketChannel.write(buffer); socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } return receivedStr; } }
NioSocketServer服务端及启动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 import java.io.IOException;import java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.util.Iterator;public class NioSocketServer { private volatile boolean open = true ; public boolean isOpen () { return open; } public void setOpen (boolean open) { this .open = open; } public void start () { try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) { serverSocketChannel.socket().bind(new InetSocketAddress (8080 )); serverSocketChannel.configureBlocking(false ); Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务端启动......" ); ServerHandler handler = new ServerHandlerImpl (); while (isOpen()) { selector.select(); System.out.println("开始处理请求......." ); Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); try { if (key.isAcceptable()) { handler.handleAccept(key); } if (key.isReadable()) { System.out.println(handler.handleRead(key)); } } catch (IOException e) { e.printStackTrace(); } keyIterator.remove(); } System.out.println("完成请求处理。" ); } } catch (IOException e) { e.printStackTrace(); } } } public class ServerMain { public static void main (String[] args) { NioSocketServer server = new NioSocketServer (); new Thread (() -> { try { Thread.sleep(60 * 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } finally { server.setOpen(false ); } }).start(); server.start(); } }
NioSocketClient客户端及启动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import java.io.IOException;import java.net.InetSocketAddress;import java.net.SocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;import java.nio.charset.StandardCharsets;public class NioSocketClient { public void start () { try (SocketChannel socketChannel = SocketChannel.open()) { SocketAddress socketAddress = new InetSocketAddress ("localhost" , 8080 ); socketChannel.connect(socketAddress); int sendCount = 0 ; ByteBuffer buffer = ByteBuffer.allocate(1024 ); while (sendCount < 5 ) { buffer.clear(); buffer.put(("当前时间: " + System.currentTimeMillis()).getBytes()); buffer.flip(); socketChannel.write(buffer); buffer.clear(); int readLength = socketChannel.read(buffer); buffer.flip(); byte [] bytes = new byte [readLength]; buffer.get(bytes); System.out.println(new String (bytes, StandardCharsets.UTF_8)); buffer.clear(); sendCount++; try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } } public class ClientMain { public static void main (String[] args) { new NioSocketClient ().start(); } }
结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 服务端启动...... 开始处理请求....... 请求连接...... 完成请求处理。 开始处理请求....... 当前时间: 1567409176760 完成请求处理。 开始处理请求....... 当前时间: 1567409177761 完成请求处理。 开始处理请求....... 当前时间: 1567409178762 完成请求处理。 开始处理请求....... 当前时间: 1567409179763 完成请求处理。 开始处理请求....... 当前时间: 1567409180763 完成请求处理。 开始处理请求....... 连接断开...... 完成请求处理。
简单的了解NIO 服务端的原理后,再来看Tomcat的请求处理过程,应该会更清晰一些,从前几篇文章可知,在Tomcat启动时就就有如下两个步骤:
打开服务器套接字通道ServerSocketChannel.open()
将ServerSocket绑定到指定端口serverSocketChannel.socket().bind(new InetSocketAddress(8888))
这体现在NioEndpoint类中的initServerSocket()方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected void initServerSocket () throws Exception { if (!getUseInheritedChannel()) { serverSock = ServerSocketChannel.open(); socketProperties.setProperties(serverSock.socket()); InetSocketAddress addr = (getAddress() != null ? new InetSocketAddress (getAddress(), getPort()) : new InetSocketAddress (getPort())); serverSock.socket().bind(addr, getAcceptCount()); } else { Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { serverSock = (ServerSocketChannel) ic; } if (serverSock == null ) { throw new IllegalArgumentException (sm.getString("endpoint.init.bind.inherited" )); } } serverSock.configureBlocking(true ); }
我们又知道Poller维护着Selector,在NioEndpoint的内部类Poller中可以看到selector的一些操作。 我的例子中是没有用到serverSock.accept()方法来监听客户请求的,Tomcat中用Acceptor来监听请求,获取SocketChannel,并封装成NioChannel注册到Poller中。
下面分几篇文档来分析接收到请求,封装的请求的过程。