tomcat处理请求

tomcat 4.1.30启动过程的源码分析
作者:hua_jacky1977 来源:linuxsir (2005-03-24 11:14:32)

前几天为了解决sinpool兄的《多线程的问题。》一帖,专门看了一下tomcat 4.1.30的源码,
其中重点研究了tomcat的启动这一部分,个人感觉tomcat的源码还是写的很清楚易懂,值得一看。
(以前看过struts的部分代码,感觉也比较经典)
然后我看后的代码整理了一下,附在下面,希望对其他人有用,也希望感兴趣的兄弟可以多看看好的代码,
肯定对自己的程序设计和代码质量颇有益处。

一. 启动类(包含main()方法的类):
org.apache.catalina.startup.Bootstrap
这个类是tomcat的启动类,主要按照如下步骤,进行主要的启动工作:
1. 创建3个ClassLoader:common,catalina和share,它们对应tomcat的3个Classloader,我想对tomcat
的classloader有研究的兄弟对这个肯定不陌生,其中common classloader是紧跟在系统的classloader(也就是
系统环境变量中设置的CLASSPATH所对应的classloader),而catalina classloader是common的子classloader,是tomcat
运行所需要的类的classloader,而shared classloader也是common的子classloader,是和catalina平级的classloader,
之所以说是shared classloader,是因为它是所有tomcat下面发布的webapp classloader(每一个web app都有一个自己的classloader)
的父classloader。它们这3个classloader分别读取tomcat home下面的common, server和shared三个目录里面的classes和lib目录,
用于初始化自己所控制的类库和资源。

2. 创建启动一个的org.apache.catalina.startup.Catalina类的实例,并调用它的process方法,这里使用的是java的reflection技术。
然后调用这个实例的process方法,并把Bootstrap接受到的命令行参数传递进去了,这里Bootstrap类并没有解析传给它的命令行参数。
当然在调用process之前还使用setParentClassLoader方法设置了一下父classloader。这里简单介绍一下有关classloader的一个重要
特性,就是如果classloader要load一个类时,不是自己先找,而是先把这个任务委派给自己的父classloader,然后自己的父classloader
也不找,在把这个任务委派给自己的父classloader,直到找到最顶层的classloader,然后再自顶向下的找对应的这个要load的类的定义,
如果那个classloader先找到,就返回。所以接合上面第一点介绍的tomcat中3个classloader,大家就可以明白tomca的classloader找类
的顺序了, 这个对程序开发人员来说特别重要。我想使用过tomcat或者其他app server的兄弟肯定碰到过一个类明明存在可就是找不到,
或者总是找到一个老的版本,我想主要是在多个地方放置的原因,或者哪里有重名的类:-)


二.org.apache.catalina.startup.Catalina类
现在程序转到org.apache.catalina.startup.Catalina类里面的process方法。
这个方法首先设置一下catalina的home和base目录,然后通过arguments方法解析命令行参数,
最后调用execute()方法启动server。而execute方法很简单,就是根据arguments解析的命令行参数,
决定是启动server,还是stop server,如果是start server,就调用start方法,而下面重点讲一下这个start()方法,
因为才算是一个真正开始的启动tomcat的地方:-)
1. start方法首先使用Digester(这个东东是jakarta commons里面的一个用于解析xml文件的工具包,一开始是专门用于解析struts配置文件的,
后来被抽象成现在的一个通用工具,主要还是用来解析xml配置文件,根据一些定义的rule自动生成对应的类的实例,具体信息可以参考
apache网站上的文档)来设置tomcat配置文件,也就是/conf/server.xml这个文件的解析规则
然后通过如下代码来将配置文件中的数据转化成内存中的实例:
代码:

        File file = configFile();

        try {

            InputSource is =

                new InputSource("file://" + file.getAbsolutePath());

            FileInputStream fis = new FileInputStream(file);

            is.setByteStream(fis);

            digester.push(this);

            digester.parse(is);

            fis.close();

        } catch (Exception e) {

            System.out.println("Catalina.start: " + e);

            e.printStackTrace(System.out);

            System.exit(1);

        }   
    


转换的规则如下(我只作一些简单的介绍),例如配置文件中的
a. Server对应可以产成一个org.apache.catalina.core.StandardServer类(这个类很重要,是tomcat server的实现)
b. Server/GlobalNamingResources对应生成org.apache.catalina.deploy.NamingResources类
而大家比较熟悉的监听8080端口的类配置如下:
c. Server/Service/Connector:org.apache.catalina.connector.http.HttpConnector
d. Server/Service/Engine/Host/Context/:org.apache.catalina.core.StandardContext
有兴趣的兄弟可以参考jakarta commons里面的Digester文档和org.apache.catalina.startup.Catalina
这个类里面的createStartDigester方法.
在这段代码之后,一个叫server的变量已经通过Digester工具生成了,它将会用于启动tomcat。
2. 然后程序进行了一些server启动前的设置工作,例如重定向log输出流等等。而server启动的代码如下:
代码:

        // Start the new server

        if (server instanceof Lifecycle) {

            try {

                server.initialize();

                ((Lifecycle) server).start();

                try {

                    // Register shutdown hook

                    Runtime.getRuntime().addShutdownHook(shutdownHook);

                } catch (Throwable t) {

                    // This will fail on JDK 1.2. Ignoring, as Tomcat can run

                    // fine without the shutdown hook.

                }

                // Wait for the server to be told to shut down

                server.await();

            } catch (LifecycleException e) {

                System.out.println("Catalina.start: " + e);

                e.printStackTrace(System.out);

                if (e.getThrowable() != null) {

                    System.out.println("----- Root Cause -----");

                    e.getThrowable().printStackTrace(System.out);

                }

            }

        }

    


其中server这个变量就是在刚才Digester解析时创建好的。
(当时这个地方我看了很长时间,后来才发现是这样的,因为以前不太了解Digester这个东东)。
然后大家可以看到server启动主要是分3步:
1. initialize方法进行server启动的初始化操作
2. start方法启动server,主要是server中的的service和service中的connector
3. await方法等待server shutdown
其中我重点给大家介绍一下initialize方法和start方法
initialize方法:
这里面只有一个主要任务,就是逐次调用server中所有定义的service的initialize方法,
而每个service的initialize方法中调用这个service中定义的所有connector的initialize方法,
而connector的initialize方法则是创建一个serversocket用于接受客户端的请求就结束了。
如果大家看一下tomcat下面conf/server.xml,就可以发现,tomcat默认只定义了一个service叫做Tomcat-Standalone,
而下面只有默认定义了3个connector:
1. 8080端口的http connector
2. 8443端口的http ssl connector
3. 8009端口的Coyote/JK2 AJP 1.3 Connector
我想大家对这3个端口一定不陌生吧。
start方法:
这个方法里面有一个tomcat很重要,也是我认为tomcat设计对一个亮点,就是Lifecycle这个东东,它很象一个bus(总线)。
我想大家进行过程序设计的一定知道,开始设计的时候总要根据一个原则分出几个模块来,是为了代码分块,或者将
一部分功能相似的代码组织成一个模块,这样比较清楚,例如一个进销存系统会有采购,销售,库存和财务等模块,但是
我想很多人也碰到过这样的情况就是虽然分了模块但是如果在开发完毕以后,另外一个客户说只想要其中的销售模块,我想
大部分的开发人员肯定傻眼,因为虽然当时设计的时候分了模块,但是这些模块编写的时候却是交织在一起,互相的接口定义
很模糊,基本上都是直接调用另一个模块的方法,这样肯定分不开。而tomcat的这个Lifecycle的设计理念就可以解决这个问题的
一部分,它的原理就象是一个bus(总线),例如一个模块做完一个动作以后,例如销售模块创建好一个订单后,本来要直接调用
库存模块的api锁住一部分库存(我只是随便举个例子,实际业务不一定是这样),这样销售模块就需要依赖库存模块了。但是使用了
bus方式。我们就可以在订单创建后,向bus上发送一个订单创建的消息,而总线有一个事件注册机制,有点象swing的event,listener,
例如库存模块有一个listener专门用于监听订单创建的消息,进行处理,这样2个模块就互不依赖了。有兴趣的兄喜可以看看jcp上面
的一个叫做infobus的专题。
当然这个方式只是解决有效降低模块偶合度的一个方面(因为有的时候必须要直接调用另外一个模块的接口,
例如库存模块一定要直接缺德一个销售订单的信息,那么就需要定义一个接口类来描述订单的详细信息啦,这里就不具体解释了,
有空可以专门发个帖子跟大家探讨这个问题:-) ),就是不要显式触发另一个模块的某个动作,而是通过bus机制来发送消息,
而每个模块都有一个自己的handler,会监听bus,对自感兴趣的事件进行处理。tomcat的Lifecycle就是这个东西。
下面再回到start方法:
1. 它首先向总线发送了2个事件:BEFORE_START_EVENT和START_EVENT
2. 然后调用每个service的start方法,最后发送AFTER_START_EVENT消息通知其他程序
而service的start方法主要进行的动作如下:
1. 发送BEFORE_START_EVENT消息
2. 调用container的start方法
3. 然后调用connector的start方法
4. 最后发送AFTER_START_EVENT消息.
而connector的start方法就是大家最熟悉的socket编程了,大家可以参看org.apache.catalina.connector.http.HttpConnector这个类,
主要是使用java里面的多线程操作,初始化一个HttpProcessor的线程池,然后通过wait方法阻塞住每个HttpProcessor线程,只有
当接受到一个http请求时,在通过notify方法激活HttpProcessor线程,让其处理用户的http请求。

到此为止主要简单介绍了一下tomcat 4.1.30的启动过程,下次有机会的话,可以再看看它的webapp的deploy的管理部分的代码,然后和大家分享。如果大家对我写的帖子有什么意见的话,也欢迎批评指正,希望感兴趣的兄弟可以一起探讨:-) 废话不说了,很晚了该睡觉了,祝大家周一工作愉快
(http://www.fanqiang.com)

Tomcat 原理解说:Web容器功能分析

Tomcat 原理解说:Web容器功能分析

Tomcat的模块结构设计的相当好,而且其Web 容器的性能相当出色。JBoss直接就使用了Tomcat的web容器,WebLogic的早期版本也是使用了Tomcat的代码。
Web容器的工作过程在下面的第二个参考文档中的文档已经说得相当清楚,我也就不再重复说了。如果不清楚调用过程,需要先看这个文档。这里分析一下Connector的处理过程。
1. 一个简单的Web Server示例
这个例子也是从网上找得,不知道原作者,也就不在参考资料中引用了。
这个启动服务的主程序。
public class HttpServer {
public static void main(String args[]) {
  int port;
  ServerSocket server_socket; // 读取服务器端口号
  try {
   port = Integer.parseInt(args[0]);
  } catch (Exception e) {
   port = 8080;
  }
  try {
   // 监听服务器端口,等待连接请求
   server_socket = new ServerSocket(port);
   System.out.println(”httpServer running on port “
     + server_socket.getLocalPort());
   // 显示启动信息
   while (true) {
    Socket socket = server_socket.accept();
    System.out.println(”New connection accepted “
      + socket.getInetAddress() + “:” + socket.getPort());
    // 创建分线程
    try {
     HttpRequestHandler request = new HttpRequestHandler(socket);
     Thread thread = new Thread(request);
     // 启动线程
     thread.start();
    } catch (Exception e) {
     System.out.println(e);
    }
   }
  } catch (IOException e) {
   System.out.println(e);
  }
}
}

下面是创建输出的线程
public class HttpRequestHandler implements Runnable {
final static String CRLF = “\r\n”;
Socket socket;
InputStream input;
OutputStream output;
BufferedReader br;
// 构造方法
public HttpRequestHandler(Socket socket) throws Exception {
  this.socket = socket;
  this.input = socket.getInputStream();
  this.output = socket.getOutputStream();
  this.br = new BufferedReader(new InputStreamReader(socket
    .getInputStream()));
}

// 实现Runnable 接口的run()方法
public void run() {
  try {
   processRequest();
  } catch (Exception e) {
   System.out.println(e);
  }
}

private void processRequest() throws Exception {
  while (true) {
   // 读取并显示Web 浏览器提交的请求信息
   String headerLine = br.readLine();
   System.out.println(”The client request is ” + headerLine);
   if (headerLine.equals(CRLF) || headerLine.equals(”"))
    break;
   StringTokenizer s = new StringTokenizer(headerLine);
   String temp = s.nextToken();
   if (temp.equals(”GET”)) {
    String fileName = s.nextToken();
    fileName = “.” + fileName;
    // 打开所请求的文件
    FileInputStream fis = null;
    boolean fileExists = true;
    try {
     fis = new FileInputStream(”D:/workspace/tomcat/bin/”+fileName);
    } catch (FileNotFoundException e) {
     fileExists = false;
    }
    // 完成回应消息
    String serverLine = “Server: a simple java httpServer”;
    String statusLine = null;
    String contentTypeLine = null;
    String entityBody = null;
    String contentLengthLine = “error”;
    if (fileExists) {
     statusLine = “HTTP/1.0 200 OK” + CRLF;
     contentTypeLine = “Content-type: ” + contentType(fileName) + CRLF;
     contentLengthLine = “Content-Length: “
       + (new Integer(fis.available())).toString() + CRLF;
    } else {
     statusLine = “HTTP/1.0 404 Not Found” + CRLF;
     contentTypeLine = “text/html”;
     entityBody = “<HTML>”
       + “<HEAD><TITLE>404 Not Found</TITLE></HEAD>” + “<BODY>404 Not Found” + “<br>usage:http://yourHostName:port/” + “fileName.html</BODY></HTML>”;
    }
    // 发送到服务器信息
    output.write(statusLine.getBytes());
    output.write(serverLine.getBytes());
    output.write(contentTypeLine.getBytes());
    output.write(contentLengthLine.getBytes());
    output.write(CRLF.getBytes());
    // 发送信息内容
    if (fileExists) {
     sendBytes(fis, output);
     fis.close();
    } else {
     output.write(entityBody.getBytes());
    }
   }
  }
  // 关闭套接字和流
  try {
   output.close();
   br.close();
   socket.close();
  } catch (Exception e) {
  }
}

private static void sendBytes(FileInputStream fis, OutputStream os)
   throws Exception {
  // 创建一个 1K buffer
  byte[] buffer = new byte[1024];
  int bytes = 0;
  // 将文件输出到套接字输出流中
  while ((bytes = fis.read(buffer)) != -1) {
   os.write(buffer, 0, bytes);
  }
}

private static String contentType(String fileName) {
  if (fileName.endsWith(”.htm”) || fileName.endsWith(”.html”)) {
   return “text/html”;
  }
  return “fileName”;
}
}
这个简单的例子说明的web服务的基本实现。Tomcat在此之上模块化出线程池,网络连接和WebHttp协议3个包。线程池可独立使用,网络连接使用池化,WebHttp直接从网络连接池中获取即可。
2. 线程池的实现
这个功能的实现在包 org.apache.tomcat.util.thread 中。
ThreadPool是线程池,是这个功能实现的核心。它使用了所有的其他类进行工作。在类图中,所有的其他类都是被此类的使用关系。
我们来看此类是如何工作得。
启动连接池的方法:
public synchronized void start() {
  stopThePool = false;
  currentThreadCount = 0;
  currentThreadsBusy = 0;

  adjustLimits();
  pool = new ControlRunnable[maxThreads];
  openThreads(minSpareThreads);
  if (maxSpareThreads < maxThreads) {
   monitor = new MonitorRunnable(this);
  }
}
方法中,根据配置情况,初始化所有线程进入备用状态。
首先定义maxThreads数目的数组,但是仅仅初始化其中minSpareThreads个。MonitorRunnable用于检查,是否空闲数目超过 maxSpareThreads个。
currentThreadCount 是当前初始化可以使用的线程数目,而currentThreadsBusy 是当前正在使用的线程数目。
使用连接池的方法:
public void runIt(ThreadPoolRunnable r) {
  if (null == r) {
   throw new NullPointerException();
  }
  ControlRunnable c = findControlRunnable();
  c.runIt(r);
}
该方法中,先寻找可用的线程,找到后在其中运行即可。
找可用线程的方法也很简单,就是将线程数组中第 currentThreadCount - currentThreadsBusy - 1 个元素取出返回,然后将此元素设成null。
线程运行完毕后,设置currentThreadsBusy– ,然后将 currentThreadCount - currentThreadsBusy - 1 的线程放回就可以了。
线程不够用就等待,等待失败就抛出异常。
说明一下上面未提到的类的功能:
ThreadPoolRunnable 这是一个接口,规定了一个线程运行时需要运行的一些动作。这里需要写一些业务逻辑的代码了。
ThreadWithAttributes 这个类从上面的代码中没有看到,这个类标识当前运行线程的一些特征,比如记录当前运行线程的一些状态。
ThreadPoolListener 用于监控ThreadPool中新增线程的情况。
ControlRunnable 这个类是ThreadPool的内部类,用于运行ThreadPoolRunnable 。当ThreadPoolRunnable 运行完毕后,通知ThreadPool回收线程。它时刻处于备用状态。此对象实例化后,就一直在死循环检查是否有它需要运行的东西。
3. 网络连接功能的实现
这个功能的实现在包 org.apache.tomcat.util.net 中。
网络连接功能构建于线程池之上,实现了一个连接服务模型。服务器打开端口,池化进入连接,为进入的连接创建工作线程。
Tomcat的网络连接两个主要的应用是1. 自己提供的web应用。2. 给Apache提供的web应用。这两个过程的解析过程都是一样的。仅仅在于网络连接协议有差别而已。两个应用都使用此包的功能实现。

PoolTcpEndpoint是核心,它使用了ThreadPool。TcpWorkerThread通过调用接口TcpConnectionHandler来完成一次连接需要完成的工作。TcpConnection标识了一个连接对象。
PoolTcpEndpoint的初始化方法代码很简单,在构建器中创建或引用ThreadPool,在初始化时创建ServerSocket,用于侦听客户端连接。
下面是初始化方法
public void initEndpoint() throws IOException, InstantiationException {
  try {
   if (factory == null)
    factory = ServerSocketFactory.getDefault();
   if (serverSocket == null) {
    try {
     if (inet == null) {
      serverSocket = factory.createSocket(port, backlog);
     } else {
      serverSocket = factory
        .createSocket(port, backlog, inet);
     }
    } catch (BindException be) {
     throw new BindException(be.getMessage() + “:” + port);
    }
   }
   if (serverTimeout >= 0)
    serverSocket.setSoTimeout(serverTimeout);
  } catch (IOException ex) {
   // log(”couldn’t start endpoint”, ex, Logger.DEBUG);
   throw ex;
  } catch (InstantiationException ex1) {
   // log(”couldn’t start endpoint”, ex1, Logger.DEBUG);
   throw ex1;
  }
  initialized = true;
}

启动的方法同样简单,仅仅将TcpWorkerThread作为线程池的工作线程,启动连接池,就大功告成了。
public void startEndpoint() throws IOException, InstantiationException {
  if (!initialized) {
   initEndpoint();
  }
  if (isPool) {
   tp.start();
  }
  running = true;
  paused = false;
  if (isPool) {
   listener = new TcpWorkerThread(this);
   tp.runIt(listener);
  } else {
   log.error(”XXX Error - need pool !”);
  }
}

侦听的细节包装在TcpWorkerThread类中。运行时,它在ServerSocket端口侦听。当发现有连接进入后,立刻开启一个新线程继续侦听,本线程开始处理连接。下面是代码:
public void runIt(Object perThrData[]) {
  // Create per-thread cache
  if (endpoint.isRunning()) {
   // Loop if endpoint is paused
   while (endpoint.isPaused()) {
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     // Ignore
    }
   }
   // Accept a new connection
   Socket s = null;
   try {
    s = endpoint.acceptSocket();
   } finally {
    // Continue accepting on another thread…
    if (endpoint.isRunning()) {
     endpoint.tp.runIt(this);
    }
   }
   // Process the connection
   if (null != s) {
    TcpConnection con = null;
    int step = 1;
    try {
     // 1: Set socket options: timeout, linger, etc
     endpoint.setSocketOptions(s);
     // 2: SSL handshake
     step = 2;
     if (endpoint.getServerSocketFactory() != null) {
endpoint.getServerSocketFactory().handshake(s);
     }
     // 3: Process the connection
     step = 3;
     con = (TcpConnection) perThrData[0];
     con.setEndpoint(endpoint);
     con.setSocket(s);
endpoint.getConnectionHandler().processConnection(con,
       (Object[]) perThrData[1]);
    } catch (SocketException se) {
……
4. 协议 web http的实现
这个功能的实现在包 org.apache.coyote.http11 中。
对于Http协议的实现核心类是Http11Protocol。具体功能的实现类有MXPoolListener(实现ThreadPoolListener),Http11ConnectionHander(实现TcpConnectionHandler)。
Http11Protocol的初始化方法比较简单,就是设置一下让网络连接开始运行。
Http11ConnectionHander 则初始化类Http11Processor,由它解析请求的字符串,交给生成此Connection的Connector的Container,也就是 Engine完成。Engine通过递归,解析应返回用户的数据。这个过程在参考文档中有介绍了。





假设来自客户的请求为:
http://localhost:8080/wsota/wsota_index.jsp

1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应
3) Engine获得请求localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机Host
4) Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
5) localhost Host获得请求/wsota/wsota_index.jsp,匹配它所拥有的所有Context
6) Host匹配到路径为/wsota的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)
7) path="/wsota"的Context获得请求/wsota_index.jsp,在它的mapping table中寻找对应的servlet
8) Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
9) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
10)Context把执行完了之后的HttpServletResponse对象返回给Host
11)Host把HttpServletResponse对象返回给Engine
12)Engine把HttpServletResponse对象返回给Connector
13)Connector把HttpServletResponse对象返回给客户browser




tomcat的源代码分析:http://fanqiang.chinaunix.net/program/code/2005-03-24/3008.shtml



从Tomcat源码中得到高效的软件组件

Tomcat不但为我们提供了设计和实现系统时的新思路,同时因为它是由组件或者模块构成的,所以它还为我们提供了大量可用的高效软件组件。这些组件都可以在我们的程序开发中使用。我简单列举一些,需要时可以直接从源码中取得。

    * 一些特殊集合类数据结构如池、队列、缓存等可用于服务端开发。
      \src\share\org\apache\tomcat\util\collections
    * 一个简单的钩子(Hooks)机制的实现。
      src\share\org\apache\tomcat\util\hooks
    * 一个简单线程池(ThreadPool)的实现。
      src\share\org\apache\tomcat\util\threads
    * 组件Lifecycle接口的设计和实现。
      \src\catalina\src\share\org\apache\Catalina
    * 常用的日志信息的管理(Logger)的实现。
      src\catalina\src\share\org\apache\catalina\logger
    * 对xml格式的配置信息进行处理(XmlMapper)的实现。
      src\catalina\src\share\org\apache\catalina\util\xml
    * 对socket通讯的高级管理和实现(net)。
      \src\catalina\src\share\org\apache\catalina\net

通过以上对Tomcat的简单的介绍,我们可以看出,作为一个开放源码的项目,Tomcat不但为我们提供了一个应用的平台,同时它还为我们提供了一个学习和研究设计模式、面向组件技术等理论的实践平台。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值