设为首页 收藏本站
查看: 934|回复: 0

[经验分享] jetty_connector

[复制链接]

尚未签到

发表于 2017-2-25 12:09:20 | 显示全部楼层 |阅读模式
1.Connector的继承体系
  jetty的connector继承体系如下图:

  我们可以看到,首先AbstractConnector实现了lifecycle的接口和connector接口,事实上,connector的生命周期应该和容器是差不多的,会一直存在。然后看到AbstractConnector持有了到server,ThreadPool等的引用,基本上,持有了Server的引用,就可以触及Threadpool和Handler,整个关联关系就打通了。
  然后可以看到每个具体的Connector,首先都有一个ServerSocketChannel(或者ServerSocket),对于SocketConnector和BlockingChannelConnector这样block的连接模式都有一个Endpoint的Set来存放目前所有的连接。对于Select型的,则委托给SelectorManager来管理。
  HttpBuffers是读取请求头和body的缓冲区,估计是出于减少内存分配的考虑,使用了ThreadLocal的变量,具体有requsetBuffer和responseBuffer,他们被具体connection的parser和generator引用。当然这样也增加了复杂度,特别是在continuation的情况下。
  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
  这个比较复杂,先上一个类图

  其中蓝色对象的生命周期是一个请求的生命周期,黄色的生命周期是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传入。)

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

  再重复一下,这里其实从HttpConnection开始,各种connector的处理就一样了。
  总结一下,jetty的selectchannel处理模型是这样的:

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,然后开始写。
  一些配置:
maxIdletime,最大空闲时间,主要有3钟作用(类似于apche的timeout+keepalivetimeout)
1.连接空闲的最大时间,后台有一个线程,会定时轮询所有的连接(endpoint)是否超过了最大时间,如果超过则关闭.在接收到请求等时候会更新endpoint的对应属性。
2.阻塞读取请求头或者体的最大阻塞时间。
3.阻塞写时的最大阻塞时间。
在非nio的情况下,直接设置为socekt.setSoTimeout

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-347049-1-1.html 上篇帖子: jetty启动报错 下篇帖子: Jetty安装部署
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表