`
uule
  • 浏览: 6306411 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

《How Tomcat Works》读书笔记(三):Tomcat default connector

 
阅读更多

HttpConnector 与 HttpProcessor 同步控制

Tomcat学习 HttpConnector和HttpProcessor启动流程和线程交互

  

 

Chapter 4: Tomcat default connector

何为default Connector?其实这里指的是tomcat最初设计时使用的Connector,尽管问题多多,现在已经被coyote所取代,但作为教学用例,default Connector仍然不失为一个优秀的组件,值得一学!

这一章的目的是系统的讲述tomcat的Connector,同时为介绍后面的容器作铺垫。

从这一章开始,内容越来越多也逐步深化,因此做笔记也只能摘录一部分,没有基础的人看了也许会觉得不连贯,那么推荐你先去看看原书或者它的翻译。

Http 1.1

default Connector支持Http 1.1标准,相比Http1.0,1.1最大的特色在于增加了对长连接的支持。举个例子,我们浏览网页的时候,一个html页面中,其实包含了很多资源,如图像、视频、声音,等等。每个资源都有独立的URL地址,在过去1.0时,一次只能针对一个URL获取资源,因此往往打开一个页面的操作其实包含了很多次的“客户端/服务器”之间的连接过程。反反复复的建立、断开连接是非常耗时的,而且也无疑增加了服务器的负担。在Http1.1中,通过长连接,可以一次性获取多个资源,无疑是大大节约了网络资源

客户端可以通过在请求中包含“connection: keep-alive”这一头字段来要求服务器提供长连接

 

Chunked Encoding(块编码)

有了长连接,客户端应该如何区分在一个连接中传输的多个资源呢?在过去的1.0中,由于每次只传输一个资源,因此不会有这个问题。能否使用content-length字段?不行。因为服务器并不会等到一整个图像文件都准备好了才传给你,往往是一次传若干字节,这种做法相当于操作系统的“分时”,有利于提高并发性和用户的使用感受。专家们最后终于想出一个办法,就是在每次传输时加上一些信息表示这次要传多少字节,例如:

1D/r/n 
I'm as helpless as a kitten u 
9/r/n 
p a tree. 
0/r/n

通过“十六进制数字”+“/r/n”,代表一次传输的字节数,最后的“0/r/n”表示一次会话的结束。但书上并没有说一次会话就相当于传一个资源,因此到底如何区分多个资源,还不是很清楚,需要研究Http协议才能知道答案。

Connector接口

二话不说,上图:

image

tomcat里面最基本的两个接口应该就是Connector和container了,接口本身很简单很抽象,但衍生出来的东西却非常复杂。

从图中可以看出,Connector和container是一一对应的,Connector有setContainer方法,但container没有类似的setConnector,说明只有Connector才知道container的存在,反之是不成立的。Connector是为了把Request和Response传递给container;而container不必理会谁给他Request,只要能拿到Request、再把响应写入Response就行了

此外,一个HttpConnector中包含了多个HttpProcessor,目的是为了支持多线程,可以同时处理多个Socket连接。

 

多线程处理Socket

old version

先来回顾之前的实现:

while (!stopped) { 
       Socket socket = null; 
       try { 
         socket = serversocket.accept(); 
       }        catch (Exception e) { 
         continue; 
       } 
       // Hand this socket off to an Httpprocessor 
       HttpProcessor processor = new Httpprocessor(this); 
       processor.process(socket); 
     }

这种做法的后果是,整个处理流程完全是同步的,因为 processor.process 方法是同步的。这样只有一个请求完全处理完毕后,processor.process 才会返回,while循环才能继续,效率之低可想而知,根本应付不了几个并发请求。

new version

既然找到了问题所在,接下来就要提出解决方案。很自然的想法是:线程。

大致思路是这样的:通过一个堆栈,我们保存若干数量的HttpProcessor,这些HttpProcessor都实现了Runnable接口。HttpConnector和HttpProcessor分别代表了生产者和消费者。一方面,HttpConnector获得一个个的Socket,放入空闲的HttpProcessor中,HttpProcessor处理完毕后,通过recycle方法将自己“回收”。如果没有空闲的HttpProcessor,则HttpConnector会将该Socket丢弃,直接关闭。

说到生产者和消费者这种同步模型,那么控制同步的“关键域”是什么呢?此处其实是一个布尔变量available,表示此时HttpProcessor是否空闲。HttpConnector调用HttpProcessor的同步的assign方法,将Socket交给HttpProcessor,如果available为假,那么assign方法调用完成直接返回,如果为真,则HttpConnector就wait在那里直到可用为止。HttpProcessor本身则采取相反的策略。

any question?

仔细推敲一下,会觉得这里的算法有些奇怪。首先,HttpProcessor是放在堆栈中的,凡是“非空闲”的HttpProcessor都不在堆栈中,它自己忙完了才会recycle回去,所以HttpConnector从堆栈中取出、并调用assign时的HttpProcessor肯定是“空闲”的;其次,让HttpConnector专门“wait”在那里,等于是整个tomcat都卡在那里,无法再接受新的Socket,不合情理。希望能有高人指点一下其中的原因,到底是bug,还是有更深层次的考虑。

Request & Response

Request的主要功能基本没有变,Facade依然存在,但是其继承关系比原来复杂了许多,书中也没有一一解释,让人云里雾里,或许后面的章节会有补充。

image

 

在HttpProcessor取得新的Socket后,依旧是通过process方法对Socket进行处理,流程上还是按照之前的做法,依次解析

  1. Connection:检查有没有使用代理
  2. Request:类似第三章
  3. Headers:类似第三章,但是用char[]取代String作为头字段的名称,例如:static final char[] AUTHORIZATION_NAME =  "authorization".toCharArray();
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics