Tomcat版本 8.5.x
Tomcat中通过NIO对HTTP请求的处理,最终会落到NioEndpoint类来完成。
NioEndpoint在启动之后,会创建以下几个组件:
processorCache: 包含了SocketProcessor实例的栈eventCache: 包含了PollerEvent实例的栈。nioChannels: 包含了NioChannel实例的栈。executor: 工作线程池ThreadPoolExecutorpollers: 轮询线程数组Polleracceptors: 用于接收请求连接的线程数组Acceptor
processorCache
包含了SocketProcessor实例的栈,复用其中的对象,存储用来处理已接入的HTTP请求。这里之所以要复用对象,是为了免除GC操作。
SocketProcessor会调用ConnectionHandler#process方法处理已接入的请求,进而使用Http11Processor处理HTTP 1.1的请求。
eventCache
一个包含了PollerEvent实例的栈,复用其中的对象,存储Poller要处理的事件。这里之所以要复用对象,是为了免除GC操作。
nioChannels
包含了NioChannel实例的栈,复用其中的对象,表示一个要处理的NIO连接。这里之所以要复用对象,是为了免除GC操作。
NioChannel中组合了SocketChannel和SocketBufferHandler,可用于完成数据的读写操作。
Acceptor
连接器,用于接收HTTP请求,完成最初的工作:
- 计算连接数,使用基于AQS的
LimitLatch来计算已接入的连接,过超过线程,则排队等待 - 调用
ServerSocketChannel.accept()获取一个连接 - 针对获取到的连接设置属性:
- 将获取到的连接设置非阻塞属性,
socket.configureBlocking(false); - 从
nioChannels中复用/创建NioChannel实例 - 将
NioChannel实例以PollerEvent的形式注册到pollers中的线程,监听相应的事件Poller线程的选择方式是轮询式PollerEvent包含:NioSocket实例- 新创建
NioSocketWrapper实例,并注册SelectionKey.OP_READ事件 NioEndpoint.OP_REGISTER事件
- 将获取到的连接设置非阻塞属性,
Poller
事件轮询器,所要做的工作主要是
- 轮询添加到
Poller中的事件PollerEvent,加以处理 - 等待
selector的返回,处理准备好的selectKey - 处理超时事件
轮询添加到Poller中的事件PollerEvent,加以处理:
- 如果
PollerEvent是事件类型是NioEndPoint.OP_REGISTER,则在selector上注册SelectionKey.OP_READ事件 - 如果
PollerEvent不是事件类型是NioEndPoint.OP_REGISTER,则在当前SelectionKey上添加当前PollerEvent的事件类型 - 重置该
PollerEvent事件
通过上述步骤完成对Poller中所有事件的处理,以便selector可以开始获取可用的事件。
等待selector的返回,处理准备好的selectKey:
- 如果是要发送文件的,则NioEndpoint#processSendfile方法处理
- 对于其他HTTP请求
- 对
selectKey取消注册当前的事件 - 处理可读/可写事件,将请求包装为
SocketProcessorBase实例,交给executor来执行 - 撤销该
selectKey
- 对
处理超时事件:
- 遍历
selector中的selectKey,包括准备好和未准备好的 - 判断其是否超时,若是,则作为
SocketEvent.ERROR事件处理,并取消对该selectKey的注册
这里需要注意的是,无需在poller的每次循环中都处理超时事件,无端浪费操作,而且处理超时事件也没有实时性的要求。只需在以下情况下,处理超时事件即可:
selector超时- 到达下一次超时时间
socket被关闭
处理HTTP请求
对HTTP请求的处理,实际是在Http11Processor中通过AbstractProcessorLight#process方法完成的:
- 准备输入/输出缓冲:
Http11InputBufferHttp11OutputBuffer - 解析请求行,
Http11InputBuffer#parseRequestLine - 解析请求头,
Http11InputBuffer#parseHeaders - 是否要升级协议,将请求头
Connection中的值,转换为小写,判断是否包含upgrade字样 - 准备请求对象,[
Http11Processor#prepareRequest]- 检查
COnnection请求头,判断是close还是keep-alive - 若是HTTP 1.1协议的话,检查是否有
expect请求头 - 检查
user-agent请求头 - 若uri是完整的HTTP地址,即格式为
protocol://host:port/)的,则解析出真实的uri - 添加输入过滤器
InputFilter - 解析
content-length请求头 - 解析
host请求头
- 检查
- 调用适配器
CoyoteAdapter#service处理请求- 以
Connector为起点,按照逐级调用容器,处理请求connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
- 进入业务处理层,处理业务
- 以
- 完成请求处理,退出