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接口
二话不说,上图:
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依然存在,但是其继承关系比原来复杂了许多,书中也没有一一解释,让人云里雾里,或许后面的章节会有补充。
在HttpProcessor取得新的Socket后,依旧是通过process方法对Socket进行处理,流程上还是按照之前的做法,依次解析
- Connection:检查有没有使用代理
- Request:类似第三章
- Headers:类似第三章,但是用char[]取代String作为头字段的名称,例如:static final char[] AUTHORIZATION_NAME = "authorization".toCharArray();