_ Jetty 是一个开源的servlet容器,使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行的Java应用提供web连接。jetty相较tomcat会更加的轻量级,也更加的灵活。因而也逐渐受到了众多企业的青睐。 本文不会一一介绍jetty的各个方面的特性,因为jetty默认以NIO方式来处理请求,所以本文以NIO为切入点,来探讨一下jetty处理请求的过程。 本文首先会介绍一下NIO的基本概念以及jetty的整体架构作为基础,然后介绍处理请求过程中的核心类和jetty接收处理请求的全过程。_
1.Jetty的整体架构
jetty主要有两个核心组件connector和handler构成,如下图1所示。Connector 负责监听接收客户连接请求,而 handler 组件则负责处理请求并给予响应。前面两个组件工作所需要的线程资源都直接从线程池(ThreadPool) 中获取,所有实现了Runnable接口的可执行任务都通过该线程池统一调度。
图1 jetty整体架构图
2.NIO简介
2.1 NIO的核心概念
NIO对于传统的I/O的改进主要是通道(channel),缓冲区(buffer)和选择器(selector)。Java NIO的通道类似流,但流的读写通常是单向的。我们可以从通道中读取数据,又可以写数据到通道,而且通道支持异步地读写。通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。缓冲区本质上是一块可以写入数据,也可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问这块内存。 Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便,如图2。
图2 selector结构
2.2 jetty中的NIO
以前的阻塞I/O的方式是客户端的每一次请求在服务器端开一个新的线程来进行响应,read/write的操作都是阻塞的,无论客户端有没有数据的响应,这个线程始终是开着的,CPU会不断的轮询这些线程,而且线程之间的切换的开销比较大。
NIO非堵塞技术实际是采取Reactor模式如下图3所示,时刻监听着I/O端口,如果有内容进来,会自动通知我们,这样我们就不必开启多个线程死等,从外界看,实现了流畅的I/O读写,而且也不堵塞了。
如图3 Jetty的NIO模式
mainReactor:jetty从线程池中分配一个线程专门用来接收用户的请求(accept()方法)
ServerSocketChannel server;synchronized(this) { server = _acceptChannel; }if(server!=null&& server.isOpen() && _manager.isStarted()) { SocketChannel channel = server.accept(); }
subReactor:subReactor负责多路分离已连接的channel,调用select线程,轮询注册进来的 channel。读 写网络数据,扔给worker线程池来处理。其中最主要的类是selectManager,selectManager中包含着一个selectSet的数组,每个SelectSet对象可以参见图2的结构特征,包含一个selector对象和changes数组。这里面将SelectSet设计为一个数组,应该也是分而治之的思想,让一个selector监听更少的selectionkey,那么相应的提高了并发处理的能力。
acceptor: 在接收到用户请求后,对socket进行一些配置,然后将SocketChannel注册到 selector中
| public class SelectSet implements Dumpable { private final ConcurrentLinkedQueue _changes = new ConcurrentLinkedQueue();
private volatile Selector _selector; }
|
ThreadPool:线程池比较简单,其中mainReactor与subReactor共用同一个线程池,线程池的实现类是 QueuedThreadPool
3.jetty对于请求的接收过程
3.1 核心方法的介绍
Connector:Connector组件是Jetty中可以直接接受客户端连接的抽象,一个Connector监听Jetty服务器的一个端口,所有客户端的连接请求首先通过该端口,而后由操作系统分配一个新的端口(Socket)与客户端进行数据通信(先握手,然后建立连接,但是使用不同的端口)。下图4是conncetor组件的核心类SelectChannelConnector的类图。
图4 SelectChannelConnector的类图
SelectManager:持有SelectorSet,其中SelectSet封装了NIO中的Selector,增强了其功能,也起到了负载均衡的效果。
SelectSet:在类selectSet 中,都包含一个Selector和一个change的队列,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有事件发生时,他会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后,我们从这个Channel中读取数据。
3.2 jetty请求建立
jetty 请求建立的过程时序图,见图5。
图5 jetty接受请求
server首先利用SelectChannelConnector,调用open()方法,创建ServerSocketChannel,配置其为阻塞模式,并设置localPort值。然后在doStart()方法中,它会初始化SelectManager,并启动该Manager,启动多线程,不断的调用Manager的doSelect方法。然后启动多个Acceptor线程去接收客户端的请求,有请求时就将SocketChannel注册到SelectorManager,最终由上面的select线程来处理请求。
4.jetty对于请求的处理过程
4.1 核心方法的介绍
EndPoint:在Connector启动时,它会启动多个acceptor线程用于监听在Connector中配置的端口。对于客户端的每次连接,Connector都会创建相应的EndPoint来表示该连接,EndPoint用于和Socket打交道.EndPoint最主要的方法从底层传输链路中读取数据并填入Buffer中的fill方法,以及将Buffer中的数据写入底层传输链路的flush方法。
connection:Connection用于在从Socket中读取到数据后的处理逻辑以及生成响应数据的处理逻辑,HttpConnection是Jetty中对Connection的主要实现,它表示Http客户端和服务器的一次连接,用于将Request、Response、EndPoint联系在一起。
HttpParse:Jetty使用Parser(HttpParser)来抽象HTTP请求消息和响应消息的解析类引擎。从缓存池中取缓存,将channel的请求数据读取到缓存中,并解析成Request。
HttpBuffers: 缓存池
4.2 jetty请求处理过程
图6 分配业务线程
建立好连接之后,当有数据发过来的时候,selector会监测到SocketChannel上的读的事件,接下来会创建EndPoint来表示这次连接,同时生成一个HttpConnection对象,为解析请求和handle请求准备好环境。当读事件就绪的时候,调用endpoint的schedule()方法,这个方法的主要的目的是从线程池中分配一个worker的线程来处理这个read的事件见图6。然后立刻再去监听事件,非常的简单高效。
图7 请求解析和处理
当业务线程分配成功以后,则主要是一方面负责请求的读取,将数据从socketChannel 中读取到buffer中,见图7。一方面是对请求进行解析,生成request,如果请求是完整的,那么就开始调用Server的handle()方法,处理这个request。
** 总结**:受限于能力水平,本文只是简单了介绍了jetty基于NIO的请求解析的流程,很多细节没有涉及到,希望大神们感兴趣的多多指点和补充。