tomcat源码分析(三)一次http请求的旅行-从Socket说起
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说起的更多相关文章
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- Tomcat 源码分析(转)
本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- Tomcat源码分析
前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...
- Tomcat源码分析之—具体启动流程分析
从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...
- Tomcat源码分析--转
一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...
- Tomcat源码分析——请求原理分析(下)
前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在& ...
- Tomcat源码分析——请求原理分析(上)
前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...
- Tomcat源码分析——启动与停止服务
前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...
随机推荐
- 《Git教程-廖雪峰》学习笔记
一.创建版本库 ①初始化一个Git仓库:git init ②添加文件到Git仓库:1.git add<file> ; 2.git commit 二.时光机穿梭 ①查看工作区状态,文件是否 ...
- 6.Linux的文件权限与目录配置
Linux文件属性:(- rwx r-x r-- 1 root root 2800 feb 1 11:55 test.txt 顺序按着空格排序,除了时间) 第一组字符代表这个文件是目录.文件或链接文件 ...
- 尝试自己翻译了FreeCodeCamp的文章,技术方面多认识了几种技术,文章标题:Transparency in Action Free Code Camp is Now Open Source
这是FreeCodeCamp其中一篇文章,趁着学习英文的时间,翻译这篇文章,其中讲到作者创建FCC过程,本文属于原创,第一次翻译,翻译还有诸多不足之处,请大家包含. 原文地址:https://medi ...
- java 默认修饰符
public的类.类属变量及方法,包内及包外的任何类均可以访问:protected的类.类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问:private的类.类属变量及方法,包内包外 ...
- Python开发【前端】:HTML
HTML HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他,这样就可以让浏 ...
- CentOS 系统目录解析
CentOS 系统目录解析 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMzEzNg==&mid=202463614&idx=2&sn=51 ...
- C#中一些常用的正则表达式
需要引用using System.Text.RegularExpressions; Regex r = new Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@( ...
- 学习IO流
学习IO流,不得不提到的就是JavaIO流.流就是字节序列的抽象概念,能被连续读取数据的数据源和能被连续写入数据的接受端就是流,流机制是Java及C++中的一个重要机制,通过流我们可以自由得控制文件, ...
- Arduino蓝牙模块实现通信
蓝牙参数特点 1.蓝牙核心模块使用HC-06从模块,引出接口包括VCC,GND,TXD,RXD,预留LED状态输出脚,单片机可通过该脚状态判断蓝牙是否已经连接 2.led指示蓝牙连接状态,闪烁表示没有 ...
- js 获取根目录 获取参数
function getRootPath() { //获取当前网址,如: http://localhost:8083/uimcardprj/share/meun.jsp var curWwwPath ...