tomcat源码分析(三)一次http请求的旅行

在http请求旅行之前,我们先来准备下我们所需要的工具。首先要说的就是Connector,其作为Service的子容器,承担着http请求的核心功能。那我们先来准备下一啊吧。

我们知道一次网络请求过来之后,从网络的角度来看,是经过物理层→链路层→网络层->传输层->应用层,如下图所示。

            

  我们所熟知的的Socket处于TCP(传输层),操作系统为我们提供来一套API来操作Socket,而tomcat其任务就是针对传输层过来的Socket进行包装,并实现应用层的协议,最常见的应用层协议应该算是http协议了。接下来就来具体看看tomcat是如何实现http协议(实际上tomcat还实现了ajp协议以及处理请求的。

我们这里以最常见的BIO(阻塞试IO)的方式来分析。我们先来看看tomcat是怎么处理TCP连接的。在org.apche.tomcat.util.net包主要是用于处理网络请求的,即对TCP的处理。

  首先我们来看一下org.apache.tomcat.util.net.AbstractEndPoint这个类。在Tomcat的对请求的设计当中,由专门的线程接受TCP连接,并直接将TCP连接转交给工作线程。在AbstrctEndPoint中有一个抽象的静态内部类我们来一起看一下。

public abstract static class Acceptor implements Runnable {
public enum AcceptorState {
NEW, RUNNING, PAUSED, ENDED
} protected volatile AcceptorState state = AcceptorState.NEW;
public final AcceptorState getState() {
return state;
} private String threadName;
protected final void setThreadName(final String threadName) {
this.threadName = threadName;
}
protected final String getThreadName() {
return threadName;
}
}

  可以看出在这个静态内部类中并没有实现run()方法,其实现交给子类来实现。在Tomcat中实际定义来一个 Acceptor数组来表示一组接受TCP连接的线程。我们在简单看一下其启动这个接受线程的代码实现。

protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count]; for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}

  其中count这个我们是可以在server.xml中去配置的,一般情况下,会配置1-2。也就是说接受TCP连接的线程也只是1-2个。

  说到这里,我们也应该来说重点来,就是接受线程是如何具体工作的,我们来看JIOEndPoint,这个类是AbstractEndPoint的子类,也是设计来处理TCP连接的,这个类实现了一个简单的服务器,会有一到2个监听线程来监听Socket,对于每一个TCP连接,都会从创建一个工作线程来处理。

刚刚我们说道AbstractEndPoint中的抽象静态内部类Acceptor,在其子类JIOEndPoint中也存在一个内部类,继承自Acceptor,并实现来run();方法。我们来看一下。

protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
public void run() { int errorDelay = 0; // Loop until we receive a shutdown command
while (running) { // Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
} if (!running) {
break;
}
state = AcceptorState.RUNNING; try {
//if we have reached max connections, wait
countUpOrAwaitConnection(); Socket socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
countDownConnection();
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0; // Configure the socket
if (running && !paused && setSocketOptions(socket)) {
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} else {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} catch (IOException x) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), x);
}
} catch (NullPointerException npe) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), npe);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;
}
}

  其核心在接收到TCP连接之后,即在接收到Socket,会调用processSocket(Socket socket);这个方法。我们再来关注一下这个方法。

protected boolean processSocket(Socket socket) {
// Process the request from this socket
try {
SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
wrapper.setSecure(isSSLEnabled());
// During shutdown, executor may be null - avoid NPE
if (!running) {
return false;
}
getExecutor().execute(new SocketProcessor(wrapper));
} catch (RejectedExecutionException x) {
log.warn("Socket processing request was rejected for:"+socket,x);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
log.error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}

  其核心代码在于 getExecutor().execute(new SocketProcessor(wrapper));getExecutor()会返回Executor对象(在AbstractEndPoint中createExecutor()建立了线程池),由线程池中的线程来处理该Socket。我们再来看一下SocketProccessor这个在JIOEndPoint中的内部类,这个类(注意此时已经在工作线程之中)中核心代码

            if ((state != SocketState.CLOSED)) {
if (status == null) {
state = handler.process(socket, SocketStatus.OPEN_READ);
} else {
state = handler.process(socket,status);
}
}

  从中我们可以看到实际处理又交给来Handler来处理,那么Handler怎么处理的,我们会在下一节当中具体阐述。这一节就先讲述到这里,下一节会讲述handler具体处理过程。

tomcat源码分析(三)一次http请求的旅行-从Socket说起的更多相关文章

  1. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  2. Tomcat 源码分析(转)

    本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...

  3. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  4. Tomcat源码分析

    前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...

  5. Tomcat源码分析之—具体启动流程分析

    从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...

  6. Tomcat源码分析--转

    一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...

  7. Tomcat源码分析——请求原理分析(下)

    前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...

  8. Tomcat源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...

  9. Tomcat源码分析——启动与停止服务

    前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...

随机推荐

  1. createjs学习二之flash转canvas学习1

    设置发布 或者 导出参数设置 c_lib_main c_images_main/ 导出之后的文件 打开导出的html文件可以看到如下,,可以正常显示的,,只是stop了还不能动 给舞台上某一元素添加点 ...

  2. 利用vmware 搭建分布式集群

    前言:      我们需要至少3台服务器来实现分布式,鉴于没那么多钱买真机器,从学习和开发的角度看,只有虚拟机一条路了. 软件选择:     虚拟机使用VMware软件,因为主流而且资料比较多,学习成 ...

  3. [转]SQL SERVER – Importance of Database Schemas in SQL Server

    原文地址http://blog.sqlauthority.com/2009/09/07/sql-server-importance-of-database-schemas-in-sql-server/ ...

  4. C# 迪杰斯特拉算法 Dijkstra

    什么也不想说,现在直接上封装的方法: using System; using System.Collections.Concurrent; using System.Collections.Gener ...

  5. yii框架的安装

    在安装YII2.0首先我们需要下载一个composer,何为composer,简单解释为PHP的组件工具, Composer是PHP中用来管理依赖(dependency)关系的工具,你可以在自己的项目 ...

  6. Linux学习——————基础篇

    一.linux试用 1.使用man或者info查询 2.超级简单的文本编辑器:nano 3.sync:数据同步写入磁盘,将内存中的数据写入磁盘 3.惯用的关机命令:shutdown /sbin/shu ...

  7. hdu 1556

    Color the ball Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  8. Base64 的那些事儿

    一.Base64是什么? Base64是一种编码的格式.是将信息流(字节流)按照一定的规范,重新组合,显示出完全不相关内容的编码格式. ps.定义是我自己总结的,我觉得对于知识的定义,只要简洁,不错误 ...

  9. mysql5.6.23免安装配置

    1.官网下载,并解压 2.环境变量,path下,追加mysql的bin路径D:\Program Files\mysql\bin; 3.mysql目录下的my-default.ini重命名为my.ini ...

  10. 运用node的文件系统模块批量修改文件名

      如果我们需要大批量修改一个文件中的名称,比如,删除文件名中的副本时,就可以借助node的文件系统模块,快捷快速的完成. 首先建立一个js文件(changeName.js),代码如下: // 引入f ...