How tomcat works(深入剖析tomcat)阅读笔记1-4章
How tomcat works
chapter 1 简单的web服务器
这一张的主要内容就是实现一个简单的静态资源服务器,socket编程,利用java提供的socket和serverSocket编程
整体过程如下:
HttpServer通过serverSocket监听端口,通过阻塞的accept方法接收请求,然后创建resquest和response对象,
//服务器启动主方法
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
//await方法,几乎所有的任务都在这里完成
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
//shutdown为一个成员变量,这样的服务器的较为优雅的停止方式为接收一个uri为"/SHUTDOWN_COMMAND"的请求
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
//创建request对象,把inputstream给他
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
request对象主要就做一件事情,从inputstream中解析uri
parse()
从inputstream中读取数据转成字符串,然后调用parseUri
方法
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
parseUri(String requestString)
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
//切割出uri
return requestString.substring(index1 + 1, index2);
}
return null;
}
response对象,拿到request和outputstream,按照request中的uri找到对应的资源,然后塞入outputstream中就完事儿
sendStaticResource()
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
tips:由于这个响应没有任何响应头,所以chrome现在的版本不会接受它,但是firefox可以,但也只是以纯文本形式
chapter 2 一个简单的servlet容器
第一章利用了java的socket编程构建了一个只能响应只能响应静态资源的http服务器,这一章实现一个简单的servlet容器,它只能处理简单的servlet和响应静态资源
熟悉servlet接口
servlet接口中声明了5个方法
//初始化方法
public void init(ServletConfig config)throws ServletException;
//主要的方法,接受一个request对象和response对象,然后进行处理
public void service(ServletRequest request,ServletResponse response)throws ServletException,Java.io.IOException;
//在将servlet实例从服务中移除时,servlet容器会调用servlet实例的destory()方法,清理一些资源
public void destory();
public ServletConfig getServletConfig();
public java.lang.String getServletInfo();
一个功能齐全的servlet容器处理流程
- 第一次调用某servlet时,将它载入,然后执行init()方法
- 针对每个request请求,创建ServletRequest实例和ServletResponse实例,传递给service()方法
- 关闭servlet时,调用destroy()方法,并且卸载该类
本章是一个简单的实现,就不调用init方法和destroy方法,创建好ServletRequest和ServletResponse然后作为参数传递给service方法即可
tips:在这样的servlet容器中,每次对于该servlet的请求,都会导致载入相应的servlet类,可想而知,效率是非常低的
主要类以及流程分析
本章主要包含了以下几个类:
- HttpServer1
- Request
- Response
- StaticResourceProcessor
- ServletProcessor1
- Constants
该servlet容器的UML如下
处理流程
入口类,HttpServer1,监听端口8080,构建request,response,根据是请求静态资源还是servlet分别处理
//服务器主方法
public static void main(String[] args) {
HttpServer1 server = new HttpServer1();
server.await();
}
//await
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
// check if this is a request for a servlet or a static resource
// a request for a servlet begins with "/servlet/"
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
request.parse()
和第一章一样解析uri
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
//uri = parseUri(request.toString());
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
processor.process(request, response);
请求servlet分支,接受request,response作为参数,然后调用ServletProcessor1的process方法,载入对应的servlet然后,实例化该servlet并且真正地处理请求
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// the forming of repository is taken from the createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
// the code for forming the URL is taken from the addRepository method in
// org.apache.catalina.loader.StandardClassLoader class.
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
//载入servlet
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
//实例化servlet并处理请求
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
processor.process(request, response);
发送静态资源分支,这里只是调用了以下response的sendStaticResource方法
public void process(Request request, Response response) {
try {
response.sendStaticResource();
}
catch (IOException e) {
e.printStackTrace();
}
}
chapter 3 连接器(Connector)
建立一个连接器,用来增强第二章的功能,主要就是用一种更好的方式创建request和response对象
本章应用包含三个模块:连接器模块、启动模块和核心模块
启动模块只包含了一个类:BootStrap
连接器模块可分为以下5个类型:
- 连接器及其支持类(HttpConnector和HttpProcessor)
- 表示Http请求的类(HttpRequest)及其支持类
- 表示Http响应的类(HttpResponse)及其支持类
- 外观类(HttpRequestFacade和HttpResponseFacade)
- 常量类(Constants)
核心模块包含两个类,servletProcessor和StaticResourceProcessor,具体UML如下
主要类以及流程分析
启动类BootStrap,new一个connector然后调用connector.start(),不难猜到connector的start另启了一个线程
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
HttpConnector
连接器模块主要类,负责监听端口,接收请求,然后将自己传递给HttpProcessor,并且将socket作为参数,传递给HttpProcessor的process方法
run()
public void run() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stopped) {
// Accept the next incoming connection from the server socket
Socket socket = null;
try {
socket = serverSocket.accept();
}
catch (Exception e) {
continue;
}
// Hand this socket off to an HttpProcessor
HttpProcessor processor = new HttpProcessor(this);
processor.process(socket);
}
}
public void start() {
Thread thread = new Thread(this);
thread.start();
}
HttpProcessor
负责生成resquest以及response,然后交给ServletProcessor或者StaticResourceProcessor处理
public void process(Socket socket)
public void process(Socket socket) {
//SocketInputStream是InputStream的包装类,主要提供了readRequestLine方法和readHeader方法
SocketInputStream input = null;
OutputStream output = null;
try {
//2048为指定的缓冲数组大小
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
// create HttpRequest object and parse
request = new HttpRequest(input);
// create HttpResponse object
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "Pyrmont Servlet Container");
//解析请求行
parseRequest(input, output);
//解析首部行
parseHeaders(input);
//然后就可以根据请求的uri决定交给什么processor了
//这两者就和第二章的一样了
//check if this is a request for a servlet or a static resource
//a request for a servlet begins with "/servlet/"
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
// no shutdown for this application
}
catch (Exception e) {
e.printStackTrace();
}
}
解析请求行parseRequest
private void parseRequest(SocketInputStream input, OutputStream output)
private void parseRequest(SocketInputStream input, OutputStream output)
throws IOException, ServletException {
// Parse the incoming request line
//HttpRequestLine为请求行对象,利用readRequestLine方法填充HttpProcessor的HttpRequestLine成员变量
input.readRequestLine(requestLine);
//经过上面的处理,这里就能拿到方法名了
String method =
new String(requestLine.method, 0, requestLine.methodEnd);
//uri暂时还拿不到,因为可能带参,此时的uri字符数组保存的是带参的uri
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
// Validate the incoming request line
if (method.length() < 1) {
throw new ServletException("Missing HTTP request method");
}
else if (requestLine.uriEnd < 1) {
throw new ServletException("Missing HTTP request URI");
}
// Parse any query parameters out of the request URI
//处理带参uri
int question = requestLine.indexOf("?");
if (question >= 0) {
//包请求参数塞进queryString,回头需要的时候解析
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
//检查,可略过
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
//处理jessionId
// Parse any requested session ID out of the request URI
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
// Normalize URI (using String operations at the moment)
String normalizedUri = normalize(uri);
// Set the corresponding request properties
//把请求行的属性都塞进去,至此请求行的数据解析完毕
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
}
HttpRequestLine
HttpRequestLine为请求行对象,用字符数组来存储请求行的数据,代码一瞥
public HttpRequestLine(char[] method, int methodEnd,
char[] uri, int uriEnd,
char[] protocol, int protocolEnd) {
this.method = method;
this.methodEnd = methodEnd;
this.uri = uri;
this.uriEnd = uriEnd;
this.protocol = protocol;
this.protocolEnd = protocolEnd;
}
public char[] method;
public int methodEnd;
public char[] uri;
public int uriEnd;
public char[] protocol;
public int protocolEnd;
SocketInputStream的readRequestLine方法
public void readRequestLine(HttpRequestLine requestLine)
public void readRequestLine(HttpRequestLine requestLine)
throws IOException {
// Recycling check
if (requestLine.methodEnd != 0)
requestLine.recycle();
// Checking for a blank line
//跳过空行
int chr = 0;
do { // Skipping CR or LF
try {
//调用read方法,
chr = read();
} catch (IOException e) {
chr = -1;
}
} while ((chr == CR) || (chr == LF));
if (chr == -1)
throw new EOFException
(sm.getString("requestStream.readline.error"));
//由于read方法中将pos指向了下一个字节了,需要回退一步
pos--;
// Reading the method name
int maxRead = requestLine.method.length;
int readStart = pos;
int readCount = 0;
boolean space = false;
while (!space) {
//超过method字符数组的最大长度了
// if the buffer is full, extend it
if (readCount >= maxRead) {
if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
char[] newBuffer = new char[2 * maxRead];
System.arraycopy(requestLine.method, 0, newBuffer, 0,
maxRead);
requestLine.method = newBuffer;
maxRead = requestLine.method.length;
} else {
throw new IOException
(sm.getString("requestStream.readline.toolong"));
}
}
// We're at the end of the internal buffer
//如果已经读取到inputstream读取出来的buffer的最后一个位置了
if (pos >= count) {
int val = read();
if (val == -1) {
throw new IOException
(sm.getString("requestStream.readline.error"));
}
pos = 0;
readStart = 0;
}
//正常都走到这一步
if (buf[pos] == SP) {
space = true;
}
//这里method字符数组是以空格节未的,并没有在buf[pos]=SP的时候break
requestLine.method[readCount] = (char) buf[pos];
readCount++;
pos++;
}
//以空格结尾,readCount去掉空格
requestLine.methodEnd = readCount - 1;
//下面就是解析uri和protocol了
}
SocketInputStream的read、fill方法
public int read()
throws IOException {
//起始值pos为0,count也为0
if (pos >= count) {
//修改count为本次读入的字节长度
fill();
if (pos >= count)
return -1;
}
//返回buf[pos]的那个字节
return buf[pos++] & 0xff;
}
protected void fill()
throws IOException {
pos = 0;
count = 0;
//
int nRead = is.read(buf, 0, buf.length);
if (nRead > 0) {
count = nRead;
}
}
解析首部行parseHeaders
private void parseHeaders(SocketInputStream input)
private void parseHeaders(SocketInputStream input)
throws IOException, ServletException {
//循环解析每个请求头
while (true) {
HttpHeader header = new HttpHeader();;
// Read the next header
//socketInputStream提供的readHeader方法,上面已经详细解读过readRequestLine方法了,这里不详细解读,填充header
//HttpHeader和HttpRequestLine结构也差不多
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
request.addHeader(name, value);
//对于cookie和content-length两个header需要特殊处理一下
// do something for some headers, ignore others.
if (name.equals("cookie")) {
Cookie cookies[] = RequestUtil.parseCookieHeader(value);
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("jsessionid")) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
}
}
request.addCookie(cookies[i]);
}
}
else if (name.equals("content-length")) {
int n = -1;
try {
n = Integer.parseInt(value);
}
catch (Exception e) {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
}
request.setContentLength(n);
}
else if (name.equals("content-type")) {
request.setContentType(value);
}
} //end while
}
懒加载的parameter
请求参数在被调用之前是不会解析的,在request的getParameter、getParameterMap、getParameterNames等方法被调用时,首先会调用parseParameter方法
需要注意的一点是,ParameterMap是不支持修改的,它被锁住了,很简单,用一个lock变量,修改的直接return
protected void parseParameters()
protected void parseParameters() {
if (parsed)
return;
ParameterMap results = parameters;
if (results == null)
results = new ParameterMap();
//先开锁
results.setLocked(false);
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1";
//parse请求头中的参数
// Parse any parameters specified in the query string
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
}
catch (UnsupportedEncodingException e) {
;
}
//parse请求体中的参数
// Parse any parameters specified in the input stream
String contentType = getContentType();
if (contentType == null)
contentType = "";
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
}
else {
contentType = contentType.trim();
}
if ("POST".equals(getMethod()) && (getContentLength() > 0)
&& "application/x-www-form-urlencoded".equals(contentType)) {
try {
int max = getContentLength();
int len = 0;
byte buf[] = new byte[getContentLength()];
ServletInputStream is = getInputStream();
while (len < max) {
int next = is.read(buf, len, max - len);
if (next < 0 ) {
break;
}
len += next;
}
is.close();
if (len < max) {
throw new RuntimeException("Content length mismatch");
}
RequestUtil.parseParameters(results, buf, encoding);
}
catch (UnsupportedEncodingException ue) {
;
}
catch (IOException e) {
throw new RuntimeException("Content read fail");
}
}
// Store the final results
//再把parameterMap锁上,并且将parsed置为true避免重复parse
results.setLocked(true);
parsed = true;
parameters = results;
}
chapter 4 Tomcat默认的连接器
总体介绍
第三章实现了简单的连接器,第四章来看看Tomcat默认的连接器,虽然该连接器已经被弃用,换成了另一个运行速率更快的连接器——Coyote——替代,但是还是值得一看的,相较于第三章简单的连接器,Tomcat4的默认连接器有以下优化:
- 更为全面地支持Http协议,包括从0.9到1.1,也支持1.1的新特性
- 每一个connector不再只绑定一个HttpProcessor,将会利用多线程支持多Http连接
- 大量利用字符数组,减少了代价较高的字符串操作
- 利用了池的技术,缓存了HttpProcessor对象,避免每次来都需要创建,节省了对象创建的开销
但仍然还存在着上来就直接parse所有的Header不管有没有使用,效率比较低的问题
它的主要工作原理和第三章的连接器基本类似,细微之处不同稍后源代码讨论
默认的连接器UML图如下:
首先需要注意的一点是,于第三章不同,HttpConnector来负责Request、Response的创建了,而HttpProcessor专注于parse信息,也就是填充request和response
Http1.1的新特性
持久连接
减少了频繁创建TCP连接带来的系统消耗,也减少了三次握手的次数,提高了速度,在Http1.1中,默认采用持久连接,也可以显式使用,在request首部行加上
Connection: Keep-alive
块编码
有了持久连接之后,一个TCP连接可能被共享,那么多个对象的数据可能混在一起,那么如何把它们区分开来呢?利用transfer-encoding的特殊请求头,它用来指明字节流会分块发送,对每一个块,块的长度后面会有一个CRLF,然后才是数据,一个事务以一个长度为0的块标记,“0\r\n”表示事务已经结束
//e.g利用两个块发送 I'am as helpess as a kitten up a tree.
//发送内容如下
1D\r\n
I'am as helpess as a kitten u
9\r\n
p a tree.
0\r\n
状态码100
当浏览器想发送一个比较大的请求体的时候,不知道服务器能不能接收时,可以先发送一个带有如下请求头的请求
Except: 100-continue
期待服务的回应为100-continue,收到这个回应说明可以接收,这个时候浏览器才会真正地发出这个比较大的请求,避免浪费带宽、系统资源
如果服务器收到带有上述请求头的请求,并且支持的话,就发送如下的回复
HTTP/1.1 100 continue
这样浏览器就会发送真正的请求了
主要类以及流程分析
创建ServerSocket
首先调用initialize方法
void initialize()
public void initialize()
throws LifecycleException {
if (initialized)
throw new LifecycleException (
sm.getString("httpConnector.alreadyInitialized"));
this.initialized=true;
Exception eRethrow = null;
// Establish a server socket on the specified port
try {
//调用open方法
serverSocket = open();
} catch (IOException ioe) {
...
}
if ( eRethrow != null )
throw new LifecycleException(threadName + ".open", eRethrow);
}
ServerSocket open()
private ServerSocket open()
throws IOException, KeyStoreException, NoSuchAlgorithmException,
CertificateException, UnrecoverableKeyException,
KeyManagementException
{
// Acquire the server socket factory for this Connector
ServerSocketFactory factory = getFactory();
//根据是否有限定的IP地址选择不同的工厂方法
// If no address is specified, open a connection on all addresses
if (address == null) {
log(sm.getString("httpConnector.allAddresses"));
try {
//不限定端口地址,也就是说监听任何IP的请求
return (factory.createSocket(port, acceptCount));
} catch (BindException be) {
throw new BindException(be.getMessage() + ":" + port);
}
}
// Open a server socket on the specified address
try {
InetAddress is = InetAddress.getByName(address);
log(sm.getString("httpConnector.anAddress", address));
try {
return (factory.createSocket(port, acceptCount, is));
} catch (BindException be) {
throw new BindException(be.getMessage() + ":" + address +
":" + port);
}
} catch (Exception e) {
log(sm.getString("httpConnector.noAddress", address));
try {
return (factory.createSocket(port, acceptCount));
} catch (BindException be) {
throw new BindException(be.getMessage() + ":" + port);
}
}
}
factory.createSocket(port, acceptCount)
//ServerSocketFactory中定义,在DefalutServerSocketFactory中实现
public ServerSocket createSocket (int port, int backlog)
throws IOException, KeyStoreException, NoSuchAlgorithmException,
CertificateException, UnrecoverableKeyException,
KeyManagementException {
return (new ServerSocket(port, backlog));
}
至此创建了ServerSocket并与HttpConnector的serverSocket成员变量绑定
监听请求
start()
public void start() throws LifecycleException {
// Validate and update our current state
if (started)
throw new LifecycleException
(sm.getString("httpConnector.alreadyStarted"));
threadName = "HttpConnector[" + port + "]";
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// Start our background thread
//new了一个thread 并且调用了thread的start方法
threadStart();
// Create the specified minimum number of processors
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
}
void run()
start之后进入run方法
public void run() {
// Loop until we receive a shutdown command
//stopped成员变量
while (!stopped) {
// Accept the next incoming connection from the server socket
Socket socket = null;
try {
//接收请求
socket = serverSocket.accept();
//设置超时时长
if (connectionTimeout > 0)
socket.setSoTimeout(connectionTimeout);
} catch (AccessControlException ace) {
log("socket accept security exception: " + ace.getMessage());
continue;
} catch (IOException e) {
if (started && !stopped)
log("accept: ", e);
break;
}
// Hand this socket off to an appropriate processor
//从成员变量processors,这个processor池中拿一个HttpProcessor出来
//如果池中没有,则创建一个
HttpProcessor processor = createProcessor();
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
//如果不允许创建了,则直接把socket close掉,也就是直接抛弃这个请求
socket.close();
} catch (IOException e) {
;
}
continue;
}
//把socket交给processor
processor.assign(socket);
// The processor will recycle itself when it finishes
}
// Notify the threadStop() method that we have shut ourselves down
synchronized (threadSync) {
threadSync.notifyAll();
}
}
HttpProcessor createProcessor()
private HttpProcessor createProcessor() {
synchronized (processors) {
if (processors.size() > 0)
return ((HttpProcessor) processors.pop());
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
//stack中没有空闲的的processor了,并且当前Processor数量还没超过允许的最大Processor数量,则创建一个新的并返回
return (newProcessor());
} else {
//如果maxProcessors<0 说明processor数量不受限制,可以无限制创建
if (maxProcessors < 0) {
return (newProcessor());
} else {
//说明curProcessors > maxProcessors 没有空闲的processor了,也不允许创建
return (null);
}
}
}
}
HttpProcessor newProcessor()
private HttpProcessor newProcessor() {
//创建processor并且绑定connector
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
if (processor instanceof Lifecycle) {
try {
//注意!!! 这里启动了HttpProcessor线程
((Lifecycle) processor).start();
} catch (LifecycleException e) {
log("newProcessor", e);
return (null);
}
}
created.addElement(processor);
return (processor);
}
processor.assign(socket)
connector把socket交付给HttpProcessor就结束了,它并不像之前那样还需要等待HttpProcessor的执行结果,它可以去继续监听其他请求了,它这里就只负责这里就负责唤醒了一下在run方法中由于没有socket阻塞的processor
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
//available默认值为false,代表是否有新的socket过来
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
//notifyAll唤醒了谁呢?唤醒了HttpProcessor的主线程
//它在newProcessor方法中启动,然后在await方法中等待
notifyAll();
if ((debug >= 1) && (socket != null))
log(" An incoming request is being assigned");
}
处理请求
HttpProcessor
run()
public void run() {
// Process requests until we receive a shutdown signal
//stopped成员变量
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
//把processor重新又压回connector的栈内重复利用
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
await()
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
//被唤醒之后availabe被修改成了true了,将socket赋值给局部变量,并且将availabe修改为false
// 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);
}
这里引用一下原文的解释
为什么这里需要返回一个局部变量?
这样做的话,就可以在当前socket被处理完成之前,接收下一个socket???黑人问号,这个process方法不是单线程的嘛?它不是必须完成解析才能被回收嘛?怎么同时接收两个socket的
为什么还需要notifyAll呢?
防止connector在执行到processor.assign()
方法阻塞,此时available为true,connector一直阻塞,知道processor唤醒它
recycle(HttpProcessor processor)
void recycle(HttpProcessor processor) {
// if (debug >= 2)
// log("recycle: Recycling processor " + processor);
processors.push(processor);
}
处理请求的主方法
**private void process(Socket socket) **
private void process(Socket socket) {
boolean ok = true;
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputStream(),
connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
keepAlive = true;
//原来宣扬了半天的持久连接新特性就是个keepAlive变量
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
ok = false;
}
// Parse the incoming request
try {
if (ok) {
//如同第三章那样,这里负责处理填充Request,如果想追究如何处理Http1.1的新特性,可以查看,这里不细说
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol()
.startsWith("HTTP/0"))
parseHeaders(input);
if (http11) {
// Sending a request acknowledge back to the client if
// requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
}
} catch (EOFException e) {
...
}
// Ask our Container to process this request
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
//从processor绑定的connector中获取contianer然后把parse好的request和response传递给他,处理请求
connector.getContainer().invoke(request, response);
}
} catch (ServletException e) {
...
}
// Finish up the handling of the request
if (finishResponse) {
try {
//
response.finishResponse();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
request.finishRequest();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
if (output != null)
output.flush();
} catch (IOException e) {
ok = false;
}
}
// We have to check if the connection closure has been requested
// by the application or the response stream (in case of HTTP/1.0
// and keep-alive).
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
//request和response对象也是需要回收的,在newProcessor的时候调用HttpConnector的createxxx方法创建作为processor的成员变量
request.recycle();
response.recycle();
}
try {
shutdownInput(input);
socket.close();
} catch (IOException e) {
;
} catch (Throwable e) {
log("process.invoke", e);
}
socket = null;
}
处理请求(Container)
核心方法invoke,也就是之前的servletProcessor做的事情,通过类加载器找到对应的servlet然后让他处理
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) {
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());
}
}
How tomcat works(深入剖析tomcat)阅读笔记1-4章的更多相关文章
- How Tomcat works — 四、tomcat启动(3)
上一节说到StandardService负责启动其子组件:container和connector,不过注意,是有先后顺序的,先启动container,再启动connector,这一节先来看看conta ...
- How Tomcat works — 一、怎样阅读源码
在编程的道路上,通过阅读优秀的代码来提升自己是很好的办法.一直想阅读一些开源项目,可是没有合适的机会开始.最近做项目的时候用到了shiro,需要做集群的session共享,经过查找发现tomcat的s ...
- How Tomcat works — 二、tomcat启动(1)
主要介绍tomcat启动涉及到的一些接口和类. 目录 概述 tomcat包含的组件 server和service Lifecycle Container Connector 总结 概述 tomcat作 ...
- 《图解HTTP》阅读笔记--第十一章针对web的攻击技术
第十一章.针对WEB的攻击技术 ----<图解HTTP>阅读笔记攻击目标---Web简单的HTTP协议本身并不存在安全性 问题,协议本身并不会成为被攻击的对象,应用HTTP的服务器和客户端 ...
- 深入理解 C 指针阅读笔记 -- 第六章
Chapter6.h #ifndef __CHAPTER_6_ #define __CHAPTER_6_ /*<深入理解C指针>学习笔记 -- 第六章*/ typedef struct _ ...
- 深入理解 C 指针阅读笔记 -- 第五章
Chapter5.h #ifndef __CHAPTER_5_ #define __CHAPTER_5_ /*<深入理解C指针>学习笔记 -- 第五章*/ /*不应该改动的字符串就应该用 ...
- How Tomcat works — 八、tomcat中的session管理
在使用shiro的session的时候感觉对于tomcat中session的管理还不是特别清楚,而且session管理作为tomcat中比较重要的一部分还是很有必要学习的. 目录 概述 session ...
- How Tomcat works — 五、tomcat启动(4)
前面摆了三节的姿势,现在终于要看到最终tomcat监听端口,接收请求了. 目录 Connector Http11Protocol JIoEndpoint 总结 在前面的初始化都完成之后,进行Conne ...
- How Tomcat works — 七、tomcat发布webapp
目录 什么叫发布 webapp发布方式 reload 总结 什么叫发布 发布就是让tomcat知道我们的程序在哪里,并根据我们的配置创建Context,进行初始化.启动,如下: 程序所在的位置 创建C ...
- How Tomcat works — 六、tomcat处理请求
tomcat已经启动完成了,那么是怎么处理请求的呢?怎么到了我们所写的servlet的呢? 目录 Http11ConnectionHandler Http11Processor CoyoteAdapt ...
随机推荐
- 20200726_java爬虫_使用HttpClient模拟浏览器发送请求
浏览器获取数据: 打开浏览器 ==> 输入网址 ==> 回车查询 ==> 返回结果 ==> 浏览器显示结果数据 HttpClient获取数据: 创建HttpClient ==& ...
- 我叫MongoDb,不懂我的看完我的故事您就入门啦!
这是mongo基础篇,后续会连续更新4篇 大家好我叫MongoDb,自从07年10月10gen团队把我带到这个世界来,我已经13岁多啦,现在越来越多的小伙伴在拥抱我,我很高兴.我是NoSQL大家族的一 ...
- python求平均数及打印出低于平均数的值列表
刚学Python的时候还是要多动手进行一些小程序的编写,要持续不断的进行,知识才能掌握的牢.今天就讲一下Python怎么求平均数,及打印出低于平均数的数值列表 方法一: scores1 = [91, ...
- docker搭建redis集群
一.简介 docker作为一个容器技术,在搭建资源隔离性服务上具有很大的优势,在一台服务器上可以启动多个docker容器,感觉每个在容器里面部署的服务就像是部署在不同的服务器上.此次基于docker以 ...
- 【Android Studio】安卓开发初体验1——安装与试用
安装 (安装预留硬盘(C盘)空余空间16G以上,8G以上内存) Intel用户: 安装Android Studio和AVD(安卓虚拟机 Android Virtual Device) 安装过程需要注意 ...
- layui常用的公共属性
这个是今天看官网是觉得应该很有用的东西,记录在此.位置位于官网页面元素下的HTML规范:常用公共属性中.解释如下: lay-skin=" " 定义相同元素的不同风格,如checkb ...
- [阿里DIN]从模型源码梳理TensorFlow的形状相关操作
[阿里DIN]从模型源码梳理TensorFlow的形状相关操作 目录 [阿里DIN]从模型源码梳理TensorFlow的形状相关操作 0x00 摘要 0x01 reduce_sum 1.1 reduc ...
- Linux下Flask环境
一,安装python3.6.4 wget http://www.python.org/ftp/python/3.6.4/Python-3.6.4.tgz tar -xvzf Python-3.6.4. ...
- centos常用指令之-卸载
卸载centos自带java: rpm -qa|grep java // 查询javax相关 xxxxxxxxxxxxxx # 卸载1.2方式 # 1 yum -y remove java xxxxx ...
- haproxy 思考
通过代理服务器在两个TCP接连之间转发数据是一个常见的需求,然后通常部署的时候涉及到(虚拟)服务器.真实服务器.防护设备.涉及到多个ip地址相关联,改动一个IP就需要修改配置. 比如反向服务器部署的时 ...