浅析tomcat原理

浅析tomcat原理

上上个星期,看了一下how tomcat works这本书,今天捡起来看一会,发现忘得有点快,特地写点东西,加深一下记忆。因为书讲的是tomcat4,5的内容,比较旧了,所以和最新的tomcat的差距还是有点大的。而且还没看完,以后再补充吧。

而java实现web最简单的方式就是对socket和serversocket的封装。也是早期tomcat的实现方式。

计算机网络之间的通信是基于端口之间通信,对于服务器,从端口读取数据,也就是inputStream, 往端口写数据,也就是outputStream。

serverSocket = new ServerSocket(8080, 1, InetAddress
					.getByName("127.0.0.1"));
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();

HttpServletRequest是请求,也就是客户端往服务器端口发送消息,服务器从端口读取数据,所以HttpServletRequest封装了上面的inputStream。 同理HttpServletResponse封装了上面的outputStream。

ServerSocket 监听本机端口, 我们可以去浏览器输入: http://127.0.0.1:8080 然后后台读取程序中读取一个叫做index.html的文件(FileInputStream->bytes),output.write(bytes, 0, lenth). 将文件内容输出到端口,网页中就会显示内容了。是不是有点熟悉。当然tomcat肯定不是这么简单的实现,但是最基本的原理就是如此。

当然这个是静态资源,如果是动态servlet的处理,大致是通过类加载器,加载进内存,反射得到对象。然后根据request请求信息,判断是请求静态文件还是动态servlet,然后执行servlet的service(request, response)方法;

Connector 连接器

直接跳到连接器,有点太快了,书中还有很多小细节,门面设计模式, response中getWriter方法就是封装了outputStream… 推荐去看一下。

连接器,顾名思义,就是用来处理连接的。

 private int port = 8080;
 private ServerSocket serverSocket = null;

这是我直接在tomcat4源码中HttpConnector拷贝过来的两个参数,然后再来看看这个类的一个重要方法。

public void run() {
        // Loop until we receive a shutdown command
        while (!stopped) {
            Socket socket = null;
            try {
                socket = serverSocket.accept();
                ...
            } catch () {
               ...
            }
            HttpProcessor processor = createProcessor();
            ...
            processor.assign(socket);
           ...
        }
  	...
    }

一个循环,等待连接的到来,得到一个socket对象, 然后创建一个处理器,去处理这个socket的请求。然后继续等待下一个连接accept()。

当然如果tomcat这样顺序进行,那肯定是不行的,所以一个连接器有一个processor(处理器)的对象池。而每个处理器实现了runnable接口,在创建的时候就启动了线程,然后阻塞自己,直到自己调用了assign() 方法,就是上面的那个方法,然后执行处理request的方法,处理servlet还是静态资源。完成后继续阻塞自己。

所以tomcat的connector有多个processor,请求来了调用一个processor去执行,而本身不需要等待这个processor完成,继续接收下一个请求。是一种异步实现的感觉。这样可以处理多个请求,而无需阻塞。这也是早期bio的解决方案。现在的解决方案应该是nio了。

final class HttpProcessor implements Lifecycle, Runnable {

public void run() {
        while (!stopped) {
        	Socket socket = await();
            if (socket == null)
                continue;
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }
            connector.recycle(this);
        }
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }
}

就是在await()方法阻塞,然后释放,执行process完成,循环继续阻塞。

processor 处理器

首先看看这个processor做了什么吧。

  • 创建HttpServletRequest, HttpServletResponse对象。
  • 解析连接
  • 解析请求
  • 解析头部, 给request.setHeader。
  • 隐式调用了servlet的service方法。 虽然从这里开始,但并不是直接调用。

具体是怎么解析的请求,我没有深入去了解,大概就是拆分字符串,截取之类的吧,还是看看这个类最重要的方法。process() 方法。

   private void process(Socket socket) {
		    ...
        try {
            input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
        } catch (Exception e) {
			    ...
        }
        keepAlive = true;
        while (!stopped && ok && keepAlive) {
            finishResponse = true;
            try {
                request.setStream(input);
                request.setResponse(response);
                output = socket.getOutputStream();
                response.setStream(output);
                response.setRequest(request);
                ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
            } catch (Exception e) {
				...
            }
             ...//这里解析请求,给丰富request和response。
            try {
			    ...
                if (ok) {
                    // 重点。 画上!!!
                    connector.getContainer().invoke(request, response);
                }
            } catch (ServletException e) {
                    ...
            }

    }

大致就是上面,可以看出,request和response封装了输入输出流,然后解析请求, 调用了container的invoke(request, response)方法。所以我们希望见到的service方法就藏在这里面了。

Container 容器

这个可以说是tomat中最被人熟知的东西之一。tomcat4中有四大容器,以我的理解简单介绍一下, 可能有点不对。

  • Engine 引擎, 启动一个tomcat服务,也就是启动一个引擎
  • host 虚拟主机, 一个Engine启动,下面项目都会启动,localhost:8080/work1,work2
  • context 上下文, 一个项目对应一个上下文,通过map映射到不同的servlet。
  • wrapper 包装器, 一个wrapper对应一个servlet。

上面的四个都实现了Container接口,就先来谈谈wrapper吧。connector中有个方法,setContainer(Container container); 如果一个wrapper被一个connector绑定,那么回到上面画重点的方法。

connector.getContainer().invoke(request, response);
wrapper 包装器

所以点进wrapper类中找寻这个方法,发现没有,这个方法的实现在他的父类ContainerBase中有实现。

  public void invoke(Request request, Response response) throws IOException, ServletException {
        pipeline.invoke(request, response);
    }

出现了一个新的东西,叫做管道(pipeline)。点下去,会发现很麻烦,先来捋一捋Container中这些的关系。

pic

container 中包含了一个pipeline, 而pipeline执行invoke方法是创建了一个pipelineContext对象,并执行invokeNext方法。 然后pipelineContext对象就会去执行Valve的invoke方法。当然,执行方式有点像递归,一直往下,直到执行basicvalve的invoke方法,这个方法中会隐式执行service方法,然后再回去执行上一个valve。看看basicvalve的invoke方法。

public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
		...
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        ServletRequest sreq = request.getRequest();
        ServletResponse sres = response.getResponse();
        Servlet servlet = null;
        HttpServletRequest hreq = null;
        if (sreq instanceof HttpServletRequest)
            hreq = (HttpServletRequest) sreq;
        HttpServletResponse hres = null;
        if (sres instanceof HttpServletResponse)
            hres = (HttpServletResponse) sres;
		...

        try {
            if (!unavailable) {
		// 划重点。。。
                servlet = wrapper.allocate();
            }
        } catch (ServletException e) {
 	    ...
        }

        ApplicationFilterChain filterChain = createFilterChain(request, servlet);

        try {
            ...
            if ((servlet != null) && (filterChain != null)) {
   		// 划重点2。。。
                filterChain.doFilter(sreq, sres);
            }
	  ...
       }
    }

通过getContainer得到wrapper, wrapper.allocate(). 这个方法中通过反射之类的得到了servlet对象,返回。然后创建一个过滤器链, 调用doFilter方法传递request和response。我们再看看doFilter方法。

  if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) {
		servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
  } else {
		servlet.service(request, response);
  }

这个方法中,终于找到了调用service方法了。当然上面说的都是wrapper。一个wrapper只对应一个servlet,但是一个项目肯定有多个servlet,那么这就涉及到我们熟悉的Context(上下文)了。

context 上下文

我们再回到connector,connector.setContainer(context). 如果connector设置的容器是上下文。再来分析一下。想起我们之前的那个方法。

connector.getContainer().invoke(request, response);

context的invoke和wrapper一样,pipeline.invoke, 然后同样是pipelineContext.invokeNext。 那么不一样的地方在哪里呢? 那就是basicvalve, 我们称为contextValve。 我们看看contextValve的invoke方法。

public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
    	...
        Context context = (Context) getContainer();
        Wrapper wrapper = null;
        try {
            wrapper = (Wrapper) context.map(request, true);
        } catch (IllegalArgumentException e) {
            badRequest(requestURI, (HttpServletResponse) response.getResponse());
            return;
        }
        if (wrapper == null) {
            notFound((HttpServletResponse) response.getResponse());
            return;
        }
        response.setContext(context);
        wrapper.invoke(request, response);
    }

相当简洁, 就是context从request中读取到类似 /IndexServlet, 然后就会去map中搜索,找到处理这个IndexServlet的servlet。也就是wrapper,执行wrapper的invoke,又回到了上面wrapper的方法,记得上面的说的吗,一个wrapper只处理一个servlet。而一个上下文对应多个servlet。 而这里的map就是我们经常写的xml映射关系。

  <servlet-mapping>  
        <servlet-name>IndexServlet</servlet-name>  
        <url-pattern>/IndexServlet</url-pattern>  
  </servlet-mapping>  

当然这个map有一个专门的类ContextMapper, 这个就不深究如何映射的。还有上面类加载,反射servlet的方法,其实也有一个专门的接口叫Loader。我也没看太懂。

然后至于host和engine的实现,没看完,看看以后看完了有时间再来补上。原理应该差不多。

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值