您的位置:首页 > 运维架构 > Tomcat

how tomcat works 读书笔记四 tomcat的默认连接器

2014-10-09 21:26 615 查看
其实在第三章,就已经有了连接器的样子了,不过那只是一个学习工具,在这一章我们会开始分析tomcat4里面的默认连接器。

连接器

Tomcat连接器必须满足以下几个要求

1 实现org.apache.cataline.Connector接口

2 负责创建实现了org.apache.cataline.Request接口的request对象

3 负责创建实现了org.apache.cataline.Response接口的response对象

这里默认的连接器的原理很简单,就是等待http请求,创建request与response对象,然后剩下任务交给Container接口的invoke方法处理。(这里是命令模式,具体问题咱们后面谈)

public void invoke(org.apache.cataline.Request request,org.apache.cataline.Response response);

http1.1的新特性

1持久连接

2块编码

3状态码100

这三点在本章的连接器中也有体现,但本人觉得这部分不是这篇博客要说明的重点,因而不过多的作解释。

tomcat总的结构框图



其实这部分内容是在书的前言部分,本来在这一系列读书笔记一开始的时候,我就得给朋友们聊聊这部分,但当时确实没怎么看懂,就只能拖到现在了。

一个servlet容器(Container)可以对应若干个连接器(Connector)。

连接器主要干的事就是接收http请求,产生request与response,然后将处理过程交给容器;

容器干的事就是调用相应的servlet对象(准确的说是调用servlet的service方法,就用传过来的request与response作参数)

Connector接口

类图如下



注意,与第三章不同,HttpConnector与HttpProcessor是一对多的关系,为什么?用多线程,有效率,一个HttpProcessor只能同时处理一个http请求。这也是与第三章不同的地方。

接口情况如下



根据上文,大家也应该能猜出来,我在图中标示的几个方法也就是接口里面最重要的。

HttpConnector类

它既实现了上面说的Connector接口,也实现了Lifecycle接口(这个接口,我们后面再细谈)

与第三章不同,我们这里的HttpConnector类多了三个功能

1创建服务器套接字

在连接器初始化的时候,会调用HttpConnector类的open方法来填充成员变量serverSocket。

open内部是通过先产生一个DefaultServerSocketFactory,在从工厂里按照端口号及acceptCount(本connector能同时支持的http请求)来创建ServerSocket对象(其实这里我也不明白为什么不直接new出那个socket 只是为了使用者 生产者分离吗)

2维护httpprocess实例

我们刚才说了一个connector关联着多个httpprocessor对象。这些对象是以Stack的数据结构存储的(先进后出)

private Stack processors = new Stack();

  private HttpProcessor createProcessor() {

        synchronized (processors) {
            if (processors.size() > 0)
                return ((HttpProcessor) processors.pop());
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                return (newProcessor());
        }
            } else {
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }
    }


上面产生一个新processor的方法中有两个参数,maxProcessor是connector支持的最大prcessor量,另一个是目前的process量。上面的逻辑意义不解释。两个参数都是可以通过set方法设置的。

3提供http请求服务

与前面几章的类似,主要的工作都在run方法内部

public void run() {
        // Loop until we receive a shutdown command
        while (!stopped) {
            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                socket = serverSocket.accept();
               ....


熟悉的accept方法

然后就是

HttpProcessor processor = createProcessor(); //分线程
            if (processor == null) {
                try {
                    
                    socket.close();
                } catch (IOException e) {
                    ;
                }
                continue;                   //话说这个continue是干什么的?
            }
            processor.assign(socket);          //主线程


需要注意的是,上面的代码我做了注释,有两个线程了,因此processor.assign只会在主线程中调用一下,不需要等待解析的结果,具体的解析在另一个分线程里了,这样做的结果就是可以同时处理多个http请求。

只有createProcessor为什么就产生了新的线程,因为

private HttpProcessor newProcessor() {

        //        if (debug >= 2)
        //            log("newProcessor: Creating new processor");
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);

    }


答案就在 ((Lifecycle) processor).start();

HttpProcessor类

既然上面都说了creatprcessor方法会启动一个新的线程,而且也出现了异步的assign方法,那么咱们就看看HttpProcessor类的assign与run方法。

public void run() {

        // Process requests until we receive a shutdown signal
        while (!stopped) {

            // Wait for the next socket to be assigned
            Socket socket = await();

            if (socket == null)
                continue;

            // Process the request from this socket
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }

            // Finish up this request
            connector.recycle(this);      //将自己重新push进stack里
        }

synchronized void assign(Socket socket) {

        // Wait for the Processor to get the previous Socket
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log(" An incoming request is being assigned");

    }


在run里面又有一个await方法,看看

private synchronized Socket await() {

        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log("  The incoming request has been awaited");

        return (socket);
    }


里面有个available,似乎是关键,再看看

/**
     * Is there a new socket available?
     */
    private boolean available = false;


我就说一句,最开始available是false,我们creatProcessor时,调用了run方法,又调用了await方法,因为available为false,while循环起作用了,调用object的wait()这个分线程就停止在这了。

processor.assign(socket); //主线程

看这个代码,再看assign(),available为false,跳过循环,将available改为true,(阻塞自己)然后通知所有进程。

run里的await方法因为available为true得以运行,接着就是 process(socket);

Request对象

默认连接器中的request对象是org.apache.cataline.Request接口的实例,uml类图如下



Response对象

uml类图如下



处理请求

这部分确实比较麻烦,可以理解为整体就是一个大循环,当httpprocessor实例终止,或者链接断开停止循环。

下来就是对request对象与response对象的初始化工作

然后是parseConnection(),parseRequest(),parseHeaders()这三个方法分别解析连接,请求与请求头。

其中解析请求与第三章的内容差不多,对请求头的解析使用了字符数组,避免了代价高昂的字符串操作。

完成解析后,process方法会把request与response对象作为参数传递给servlet容器的invoke方法

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

简单的Container应用程序

现在再说说Container,我们使用的实现类是SimpleContainer(已经移除了StaticResourceProcessor类,因此不能再访问静态资源)

我们只看看他的invoke方法

public void invoke(Request request, Response response)
    throws IOException, ServletException {

    String servletName = ( (HttpServletRequest) request).getRequestURI();
    servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
    URLClassLoader loader = null;
    try {
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(WEB_ROOT);
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }
  }


确实,如果你看了书的前三章,就会发现这里确实没有什么可说的,很简单。

Bootstrap

public final class Bootstrap {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    SimpleContainer container = new SimpleContainer();
    connector.setContainer(container);
    try {
      connector.initialize();
      connector.start();          //注意这里还只是单纯的函数调用,目前跟线程还没关系,还没run呢

      // make the application wait until we press any key.
      System.in.read();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}


初始化部分包含了一下lifecircle的问题,不用太在意,以后我们会说。

下一节我们说说tomcat里面的命令模式

博客中图片均来自how tomcat works一书
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: