首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

jetty源码分析一-connector

2013-11-09 
jetty源码分析1-connector1.Connector的继承体系jetty的connector继承体系如下图:??1.我们可以看到,首先Ab

jetty源码分析1-connector
1.Connector的继承体系

jetty的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.SocketConnector

block模式的connector代码比较简单,基本上就是获取一个socket,丢到线程池让一个worker来处理就好了。

初始化过程大致如下:
    SocketConnector的doStart方法直接调用父类,也就是AbstractConnector的doStart方法。AbstractConnector先调用open()方法,实际委托到具体的实现类,对于SocketConnector,就是调用_serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize());然后初始化connector的ThreadPool,如未特别设置,就是取server的ThreadPool。根据getAcceptors()个数初始化Acceptor并放入线程池,线程池内会启动acceptor并调用run方法,这个run方法最核心的就是调用一个抽象方法accept(_acceptor),委托给子类实现,对于SocketConnector,就是 Socket socket = _serverSocket.accept().

至此,初始化基本完成,会有getAcceptors()个Thread处于accept状态,但使用的是同一个connector,因此对于bio来说,大于一个acceptor没有意义。

接收请求过程大致如下:
    serverSocket.accept(),取得一个socket之后,用这个socket new出一个ConnectorEndPoint在ConnectorEndPoint的构造函数中new出一个HttpConnection(传入ConnectorEndPoint本身和server的引用)并赋值给ConnectorEndPoint然后调用ConnectorEndPoint的dispatch,把ConnectorEndPoint本身放入线程池。最后ConnectorEndPoint的run方法被调用,run方法中调用其持有的HttpConnection的handle函数即可。HttpConnection的handle函数及其后面,所有的connector都是同一套代码了,主要完成http请求数据解析,request等构造,以及具体的handler处理。
?3.SelectChannelConnector

这个比较复杂,先上一个类图


jetty源码分析一-connector
?

其中蓝色对象的生命周期是一个请求的生命周期,黄色的生命周期是server的生命周期。该类图和文章前面的图有些重合,但侧重点在selectchannelConnector。其中selectchannelConnector的SelectorManager以及多个selectSet主要完成selectKey的注册,轮询等操作,selectChannelEndPoint主要持有一个connection对象以及manager对象。connection持有server的引用,以及4个重要的成员,request,respoonse,parser和generator,很对称。parser和generator又持有对buffer的引用。这些对象都是在accept后new出来的,在请求结束之后销毁。

初始化过程大致如下:
    doStart()方法先初始化 SelectorManager,设置SelectSet个数等参数,_manager.setSelectSets(getAcceptors());一个SelectorManager有多个SelectSet,放在一个数组中,SelectorManager$SelectSet是去做doselect,完成读取等工作的地方。调用super.doStart()方法,即AbstractConnector的doStart。AbstractConnector调用open()方法,实际委托到具体的实现类,对于SelectChannelConnector,工作就是调用_acceptChannel = ServerSocketChannel.open();和bind();初始化getAcceptors()个Acceptor并放入线程池,线程池内会启动acceptor并调用run方法。这个run方法最核心的就是调用一个抽象方法accept(_acceptor),委托给子类实现在Acceptor的run方法中,会调用accept(_acceptor),将具体的accept实现委托给子类完成。对于SelectChannelConnector,其accept(_acceptor)方法就是调用_manager.doSelect(acceptorID);_manager.doSelect(acceptorID)会根据acceptorID,调用一个具体的SelectSet的doSelect方法。一个SelectorManager有多个SelectSet,放在一个数组中,SelectSet的个数就是Acceptor的个数。然后SelectChannelConnector的doStart方法会启动一个线程(委托给SelectorManager,最终使用的是ServerSocketChannel的ThreadPool,如果没有特别设置,就是server的ThreadPool),在这个线程中使用ServerSocketChannel.accept来accept请求,
接收请求过程大致如下:
    _ServerSocketChannel.accept接收到请求,调用manager.register(channel);将得到的SocketChannel以轮询的方式,放入多个SelectSet的一个的队列中让SelectSet的doSelect会消费它队列中的消息,发现如果是一个SocketChannel,就为这个channel注册一个READ事件,并createEndPoint创建一个EndPoint(在创建EndPoint的过程中会创建HttpConnection),将endPoint绑定到key的attachment,调用EndPoint的schedule().doSelect方法开始调用SelectionKey key: selector.selectedKeys()。对于每个取得的key,如果是SelectChannelEndPoint,就调用SelectChannelEndPoint的schedule()方法。还会新起一个线程来判断所有SendPoint的idle超时状态。在createEndPoint()时会将创建的endPoint放到一个map中。这里是每个SelectSet都有这个map。

对于createEndPoint的过程,本质和socketConnector没有区别,就是将EndPoint和HttpConnection创建出来,并维护上面类图所示关系,大致时序如下图:(这里绕一圈的原因就是httpconnection需要持有connector的引用,然后普通的encpoint都是connector的内部类,比较好处理,select是单独的,要通过manager绕一圈,当然也可以直接把connector传入。)


jetty源码分析一-connector
?

SelectChannelEndPoint的schedule()方法会处理一些nio的操作,然后将自己的handler通过dispatch丢入线程池,让别的线程来处理后续流程,包括http请求数据解析,request等构造,以及具体的handler处理等,具体过程见下图:

?


jetty源码分析一-connector
?

再重复一下,这里其实从HttpConnection开始,各种connector的处理就一样了。

总结一下,jetty的selectchannel处理模型是这样的:

?
jetty源码分析一-connector
?

4.jetty的nio

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

热点排行