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通过递归,解析应返回用户的数据。这个过程在参考文档中有介绍了。

tomcat web容器工作原理的更多相关文章

  1. web服务器工作原理

    Web服务器工作原理概述 转载自http://www.importnew.com/15020.html 很多时候我们都想知道,web容器或web服务器(比如Tomcat或者jboss)是怎样工作的?它 ...

  2. 【转载】Web应用工作原理

    问题描述:           Web应用工作原理   问题解决:          参考资料:http://blog.csdn.net/lcore/article/details/8964642   ...

  3. Java Web程序工作原理

    Web开发的最重要的基本功能是HTTP:Java Web开发的最重要的基本功是Servlet Specification.HTTP和Servlet Specitication对于Web Server和 ...

  4. Web程序工作原理

    1.Web程序工作原理 (1)Web一词的含义 Network:[计算机]电脑网络,网 Web:[计算机]万维网(World Wide Web),互联网(Internet) Web程序,顾名思义,即工 ...

  5. Web网站工作原理解析

    Web的工作原理   Web采用的是客户机--服务器架构(Client--Server model),如下图所示,其中客户端(Client)可以通过网络连接访问另一台计算机的资源或服务,而提供资源或服 ...

  6. Web的工作原理(二)

    1.工作过程:如下图所示描述了Web的工作原理. (1) 用户打开计算机(客户机),启动浏览器程序,并在浏览器中指定一个URL(Uniform Resource Locator,统一资源定位器),浏览 ...

  7. Tomcat内部结构及工作原理学习

    Tomcat原本是Servlet/JSP的一个调试工具,后来才发展为一个Servlet/JSP的容器. Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet并把结果返回给客 ...

  8. 【Tomcat】Servlet 工作原理解析

    Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础.因而掌握 Servlet 的工作原理是成为一名合格的 Java Web 技术开发人员的 ...

  9. Tomcat内部结构、工作原理、工作模式和运行模式

    TOMCAT的内部结构 Tomcat是一个基于组件的服务器,它的构成组件都是可配置的,其中最外层的是Catalina servlet容器,其他组件按照一定的格式要求配置在这个顶层容器中.Tomcat的 ...

随机推荐

  1. [NOI.AC#32]sort 构造

    链接 50分做法(只有0,1) 根据归并排序的思想,假设我们现在已经把 \(l\dots mid\) 和 \(mid+1\dots r\) 排好序 只要把左边连续的1和右边连续的0翻转即可 inlin ...

  2. 一个比NPM更快更安全可靠的JavaScript包管理工具——Yarn

    yarn安装: npm intall -g yarn 查看安装是否成功: yarn -v yarn常用的命令以及和npm的对照如下: 更详细的请查看官方文档

  3. Linux就该这么学 20181002(第二章基础命令)

    参考链接https://www.linuxprobe.com/ 忘记密码操作 启动页面 默认按e 在linux16行后空格 rd.break ctrl + x mount -o remount,rw ...

  4. sts安装出现could not find jar:file解决办法,could not find jar:file,sts安装

    标题sts插件下载好但是安装出错 我的eclipse是4.5.2,在官方网站https://spring.io/tools3/sts/legacy下载,压缩包的名字为:spring-tool-suit ...

  5. Swift学习笔记(6):控制流

    目录: For-In While If-Else, Guard-Else Switch 控制转移 For-In 可以使用for-in语句循环遍历集合.区间.元组.字符串. // 遍历区间,返回元素值 ...

  6. WPF学习(三) - 依赖属性

    学习WPF时,我在看一本叫做“深入浅出WPF”的书.整整20页都在讲依赖性性和附加属性,反复看了几遍居然还是不懂,真是郁闷. 上一篇中WPF绑定的例子,其实已经用到了依赖属性. // 作为被绑定的目标 ...

  7. 集合类 ArrayList实现公司职员薪水管理

    package com.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStr ...

  8. Windows7下Thingworx 7安装

    1.环境准备 Windows7 64位+JDK8+Tomcat8+PostgreSQL9 2.安装JDK8和Tomcat8 这里没有什么太多值得叙述的,基本都是一路下一步. 需要注意的是,一个是Tom ...

  9. Mybatis中<resultMap>用法(主要用于一对多去重)

    一.创建部门表和员工表: 创建部门信息表`t_department`,其中包括`id`, `name` CREATE TABLE t_department (         id INT AUTO_ ...

  10. 路飞学城Python-Day10

    [37.函数-命名空间]命名空间又称为name space,顾名思义就是存放名字的地方,存什么名字呢?举例说明,若变量 x = 1,存放于内存中,那名字x存放在哪里呢?名称空间正式存放名字x和1绑定关 ...