1.Server
我们可以将服务器描述为这样的应用:
它接受客户端发送的的请求数据并进行解析,完成相关业务处理后,将处理结果作为响应返回给请求的客户端。
通常情况下我们使用Socket监听服务器指定的端口来实现该功能,因此一个简单的服务器设计如下:
我们通过start()方法启动服务器,打开socket监听服务器端口,接受客户端请求进行处理并返回响应。同时提供一个stop()方法来停止服务器并释放网络资源。
2.Connector和Container
很快我们就会发现,将请求监听和业务处理放在一起的扩展性很差。比如我们有多种不同协议(AJP和HTTP)的请求,但请求处理都是相同的时候。那么我们如何通过面向对象的方式来解决这个问题呢,自然就是将请求和处理从概念上分离。我们做了如下改进:
一个Server可以包含多个Connector(链接器)和Container(容器),其中Connector负责开启Socket并监听客户端的请求、返回响应数据;Container负责具体的请求处理。Connector和Container分别拥有自己的start()和stop()方法来加载和释放资源。
但这样设计有一个明显的缺陷,既然Server可以包含多个链接器和容器,那么如何知道来自某个Connector的请求分发给哪个Container处理呢?我们看以下一个更合理的方案:
一个Server包含多个Service(它们相互独立,只是共享一个JVM和系统类库),一个Service负责维护多个Connector和一个Container,这样来自链接器的请求只能由它所属的Service所维护的那个容器处理。
在Tomcat中,Container是一个更通用的概念,为了与tomcat组件命名一致,我们将container重新命名为Engine,以表示整个servlet引擎,修改后的设计如下:
3.Container设计
上面已经解决了网络协议和容器的解耦,但是应用服务器是用来部署并运行web应用的,它是一个运行环境,而不是业务处理系统。因此我们需要在Engine容器中支持管理web应用,当接收到connector的处理请求时,Engine容器要能找到一个合适的web应用进行处理。修改方案为:
我们使用Context来代表一个web应用,并且一个Engine容器可以包含多个Context.
这是不是一个合理的方案呢?如果一台主机承担了多个域名的服务,如www.baidu.com和mp.csdn.net,那么我们如何实现呢?当然你可以在该主机上运行多个服务器实例,但如果我们希望只运行一个服务器就实现呢?(服务器灵活部署方式的体现)
我们可以将每个域名视为一个虚拟主机,在每个虚拟主机上运行web应用,对于客户端来说,他并不知道服务端用几台主机为他服务,他只知道哪些域名能提供哪些服务,因此应用服务器将每个域名视为一个虚拟主机的方案是合理的,修改后的设计为:
我们用Host表示一个虚拟主机,一个Host可以包含多个Context.
当然一个Engine既可以包含多个Host又可以包含多个Context.
在一个web应用中,可以包含多个servlet实例以处理来自不同链接的请求,在tomcat中,Servlet被定义为Warpper,因此设计为:
有仔细阅读的小伙伴都会发现,上面我们多次提到了“容器”的概念,其实容器的含义并不相同,有时候指Engine,有时候指Context,但是他代表了能处理客户端请求并响应数据的一类组件。
我们使用Container表示容器,Container可以添加并维护子容器,因此Engine、Context、Warpper均继承自Container:
Container还有一个很重要的功能就是后台处理,在很多情况下,Container需要执行一些异步处理,而且是定期执行,如每隔30秒执行一次。tomcat对web应用文件变更的扫描就是通过该机制实现的