jetty源码分析1-connector
1.Connector的继承体系
jetty的connector继承体系如下图:
?
?
1.我们可以看到,首先AbstractConnector实现了lifecycle的接口和connector接口,事实上,connector的生命周期应该和容器是差不多的,会一直存在。然后看到AbstractConnector持有了到server,ThreadPool等的引用,基本上,持有了Server的引用,就可以触及Threadpool和Handler,整个关联关系就打通了。
?
2.然后可以看到每个具体的Connector,首先都有一个ServerSocketChannel(或者ServerSocket),对于SocketConnector和BlockingChannelConnector这样block的连接模式都有一个Endpoint的Set来存放目前所有的连接。对于Select型的,则委托给SelectorManager来管理。
?
3.HttpBuffers是读取请求头和body的缓冲区,估计是出于减少内存分配的考虑(nio默认会使用directBuffer),使用了ThreadLocal的变量,具体有requsetBuffer和responseBuffer,他们被具体connection的parser和generator引用。当然这样也增加了复杂度,特别是在continuation的情况下。
?
4.endpoint这边是每次请求新创建的对象,生命周期同请求的生命周期,而且和connector一一对应。根据是否nio,使用了stream和channel两种方式,对于channel又分为阻塞和非阻塞。每个endpoint都会持有一个httpconnection,在接收请求的时候,会调用httpconnection的handle,这样后面的处理所有connector就一样了。connection会把读取的工作委托给httpPaser,在具体的fill数据的过程还是有区别的,但都是统一接口不同实现。下面针对各个connector逐一分析。
?
?
2.SocketConnectorblock模式的connector代码比较简单,基本上就是获取一个socket,丢到线程池让一个worker来处理就好了。
初始化过程大致如下:至此,初始化基本完成,会有getAcceptors()个Thread处于accept状态,但使用的是同一个connector,因此对于bio来说,大于一个acceptor没有意义。
接收请求过程大致如下:这个比较复杂,先上一个类图
?
其中蓝色对象的生命周期是一个请求的生命周期,黄色的生命周期是server的生命周期。该类图和文章前面的图有些重合,但侧重点在selectchannelConnector。其中selectchannelConnector的SelectorManager以及多个selectSet主要完成selectKey的注册,轮询等操作,selectChannelEndPoint主要持有一个connection对象以及manager对象。connection持有server的引用,以及4个重要的成员,request,respoonse,parser和generator,很对称。parser和generator又持有对buffer的引用。这些对象都是在accept后new出来的,在请求结束之后销毁。
初始化过程大致如下:对于createEndPoint的过程,本质和socketConnector没有区别,就是将EndPoint和HttpConnection创建出来,并维护上面类图所示关系,大致时序如下图:(这里绕一圈的原因就是httpconnection需要持有connector的引用,然后普通的encpoint都是connector的内部类,比较好处理,select是单独的,要通过manager绕一圈,当然也可以直接把connector传入。)
?
SelectChannelEndPoint的schedule()方法会处理一些nio的操作,然后将自己的handler通过dispatch丢入线程池,让别的线程来处理后续流程,包括http请求数据解析,request等构造,以及具体的handler处理等,具体过程见下图:
?
?
再重复一下,这里其实从HttpConnection开始,各种connector的处理就一样了。
总结一下,jetty的selectchannel处理模型是这样的:
?
?
1.jetty目前是通过一个线程来accept,然后将取得的socketChannel丢入由2个或者多个(根据acceptor个数配置)处理器中的一个的待处理队列(SelectorManager$selectSet._changes),来处理。这几个处理线程的逻辑是,先看待处理队列有木有要处理的事情,主要有两件:
1.处理连接,新建endpoint,注册读事件
2.updatekey,处理selectkey的更新操作,把关注事件添加到selectKey。
这个事情干完后,再调用selector做select,这里主要针对的是读事件的到达,有读事件到达后,将对应的attachment取出来(一般是一个SelectsocketChannel,然后调用它的schedule,将其handler丢入线程池去等待处理)。如果在http头处理中,一次读取没有全部度出来,线程将释放,等待下次读事件的到达
对于写操作,具体的写操作一般再endpoint的flush方法,但是在write的时候,一般是先注册写事件然后wait,然后等写事件就绪的时候(也在selectSet的doselect方法中)再notifyAll,然后开始写。
?
5. 一些配置
maxIdletime,最大空闲时间,主要有3钟作用(类似于apche的timeout+keepalivetimeout)
1.连接空闲的最大时间,后台有一个线程,会定时轮询所有的连接(endpoint)是否超过了最大时间,如果超过则关闭.在接收到请求等时候会更新endpoint的对应属性。
2.阻塞读取请求头或者体的最大阻塞时间。
3.阻塞写时的最大阻塞时间。
在非nio的情况下,直接设置为socekt.setSoTimeout