在软件开发时,通常会有两种架构选择,即C/S架构和B/S架构。我们先来想一个问题:为什么用户要使用我们的软件?无非就是对这个软件的功能有需求嘛。因此,无论是C/S架构还是B/S架构,他们都包含着两个角色,一个是需要服务的,也就是用户,另外一个是提供服务的。而这两种架构的区别就在于在用户处的实现形式,即用户通过何种方式获取服务。

  • C/S架构:是Client/Server的缩写,通过特定的客户端和服务端实现通信。
  • B/S架构:是Browser/Server的缩写,通过浏览器使用URL与服务端通信。其实B/S架构是一种特殊的C/S架构,因为他在用户处的实现形式:浏览器,是C/S架构中的Client。

从上图不难看出,从某个程度来看,C/S架构和B/S架构区别就在于B/S架构多出了一个Web服务器,而C/S架构是直接访问数据库服务器的。

我们来看两个例子,同样是Java对数据库的增删改查的程序,但是可以有不同架构的实现。

C/S架构实现:控制台或者GUI程序。所有的业务代码包括对数据库的驱动注册,获取连接,预编译SQL语句,处理结果集等等,这是都是在客户端上完成的。客户端通过JDBC技术与服务器中的数据库进行连接。

B/S架构实现:Java Web程序。客户机的浏览器负责界面的显示,客户端对数据的需求通过某些协议(具体是什么,下面会讲)与Web服务器交互。接着,Web服务器连接数据库服务器进行上述C/S架构一系列的操作。

其实我们可以这样理解,对于B/S架构的实现就是把C/S架构中的客户端放到了Web服务器中。

C/S架构中客户端与数据库服务器的连接技术是JDBC技术。但是,当我们客户端移到Web服务器上,那这个移动到Web服务器上的客户端怎么与新的客户机上的浏览器建立通信呢?答案是通过TCP协议和HTTP协议,我们的浏览器是支持这些协议的。同样地,为了通信的完成,服务器也需要支持这些协议,这时候我们就需要Web服务器软件了,Web服务器可以监听TCP连接以完成TCP的建立,并且接收和响应的HTTP报文封装成Java对象,传送给程序,这就是通信的过程。

这里的Web服务器有很多种实现,对于Java Web应用来说,比较常用的就是Tomcat。

Tomcat是全世界最著名的基于Java语言的轻量级应用服务器,是一款完全开源免费的Servlet容器实现。同时,它支持HTML,JS等静态资源的处理,因此又可以作为轻量级Web服务器使用。 —— 刘光瑞《Tomcat架构解析》

什么是Servlet容器实现?

我们刚刚在上面说了B/S架构可以看成C/S的客户端移到Web服务器中,而新的客户端通过TCP协议和HTTP协议与Web服务器连接。但还有一个细节没有讲到,问题又来了,Web服务器软件怎么和C/S架构搬过来的客户端通信?

Web服务器不会直接和所有的Java业务类通信,而是把请求交给Servlet容器去处理,Servlet容器会将请求转发到具体的Servlet,这里的Servlet就是能和Web服务器进行通信的类。Servlet接口其实是Servlet容器跟Servlet类之间的接口。他相当于C/S架构中客户端的Java类和Web服务器沟通的桥梁,和上面的HTTP协议,TCP协议和JDBC技术都是一样的作用:负责建立起不同部分之间的通信

Servlet接口和Servlet容器这一整套规范叫作Servlet规范,Tomcat按这套Servlet规范的要求实现了Servlet容器,所以说Tomcat是Servlet容器的实现。

接下来来看一下Tomcat的架构,从而来探索一下Servlet规范的细节。

解释几个名词:

  • Server:服务器,代表着整个Tomcat服务器。一个Server可以有多个Service。
  • Service:服务,用于具体提供服务。一个Service由多个Connector和一个Engine组成。
  • Connector:连接器,客户端浏览器与服务器建立TCP连接的点,本质上就是一个Socket服务端。负责使用HTTP协议对请求报文的解析和响应报文组装,解析过程生成Request对象,而组装过程涉及Response对象。

到这里你可能会有疑惑Servlet容器在哪里?在上面不是说Servlet容器负责转发请求到Servlet吗?

其实这里的容器,即Container,它却代表了一类组件, 这类组件的作用就是处理接收自客户端的请求并且返回响应数据。 尽管具体操作可能会委派到子组件完成, 但是从行为定义上,它们是一致的。我们使用Container来表示容器, Container可以添加并维护子容器, 因此Engine,Host,Context,Wrapper均继承自Container。 —— 刘光瑞《Tomcat架构解析》

声明一点,引用中的Engine,Host,Context在图中都有体现,但是这个Wrapper是什么鬼?其实,在Tomcat中, Servlet定义被称为Wrapper,就是我们的Servlet类。

  • Engine:引擎,用来管理多个站点。一个Engine可以有多个Host。
  • Host :主机名,代表一个站点,也就就是说一个服务器可以放多个网站,可以节省资源,类似开一台电脑可以开多个虚拟机的感觉。一个Host可以有多个Context。
  • Context:上下文,即一个Servlet应用,与ServletContext对应。一个Context至少由一个Wrapper组成。
  • Wrapper:每个Wrapper封装着一个Servlet。

当Tomcat服务器启动的时候,他将会从conf/server.xml读取配置,这个配置文件的每一个元素都对应了Tomcat中的一个组件;通过对xml文件中元素的配置,可以实现对Tomcat中各个组件的控制。

启动Server后就会启动一个Service,Service启动多个Connector,负责外部与Service的连接,其中最主要的就是HTTP连接,默认情况下,会开启一个8080端口等待客户端的连接,当客户连接到这个Connector后,他会使用HTTP协议对请求报文的解析和响应报文组装,解析过程生成Request对象,而组装过程涉及Response对象。然后解析这个Request要访问的是哪个Host(一个Host对应一个域名),然后把请求交给相应的Host,然后Host收到请求后就会解析出用户想要访问这个Host下面的哪一个Web应用,一个Web应用对应一个Context。然后再解析要访问这个Context下的哪个Servlet,并把这个交给相应的Servlet。

每一个Servlet都继承了HttpServlet抽象类,而这个抽象类实现了Servlet接口,也就是说如果几个普通的Java类继承了HttpServlet抽象类,那么他就是支持HTTP协议的Servlet了。

接下来我们来看下HttpServlet抽象类的Service方法。

protected void service(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
{
  String method = req.getMethod();

  if (method.equals(METHOD_GET)) {
      long lastModified = getLastModified(req);
      if (lastModified == -1) {
          doGet(req, resp);
      } else {
          long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
          if (ifModifiedSince < lastModified) {
              maybeSetLastModified(resp, lastModified);
              doGet(req, resp);
          } else {
              resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
          }
      }

  } else if (method.equals(METHOD_HEAD)) {
      long lastModified = getLastModified(req);
      maybeSetLastModified(resp, lastModified);
      doHead(req, resp);

  } else if (method.equals(METHOD_POST)) {
      doPost(req, resp);
  } else if (method.equals(METHOD_PUT)) {
      doPut(req, resp);
  } else if (method.equals(METHOD_DELETE)) {
      doDelete(req, resp);
  } else if (method.equals(METHOD_OPTIONS)) {
      doOptions(req,resp);
  } else if (method.equals(METHOD_TRACE)) {
      doTrace(req,resp);
  } else {
      String errMsg = lStrings.getString("http.method_not_implemented");
      Object[] errArgs = new Object[1];
      errArgs[0] = method;
      errMsg = MessageFormat.format(errMsg, errArgs);
      resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
  }
}

这个service方法是Servlet生命周期中活动时期对应的方法,他负责接收HttpServletRequest请求和HttpServletResponse响应,然后再根据请求类型,进一步分发给不同的方法,如doGet(),doPost()等。

至此,就完成了Web服务器和从C/S架构中迁移到Web服务器的客户端的通信了。这大概就是整个Java Web应用程序的执行过程了。

笔者水平有限,不正确之处,还请指出。邮箱:wjpang69@gmail.com

©著作权归作者所有

已有 2 条评论

发表评论

正在加载 Emoji