探秘Tomcat——从一个简陋的Web服务器开始
前言:
无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影。工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件,编译该文件,将生成的class文件塞到tomcat目录下相应的jar包中去,以使其生效,但是也可以热部署,不需要这么繁琐的操作。
总之,一直以来都是习惯了tomcat的存在,没有深究tomcat的运行机制和原理,上一次对于tomcat源码的跃跃欲试还是去年的事儿了——《探秘Tomcat(一)——Myeclipse中导入Tomcat源码》。在这篇文章中,我下载了tomcat6版本的代码,并将其导入到eclipse中,时隔一年多,原来的项目还在,但是为显诚意,我还是重头做了一遍导入tomcat源码到eclipse的操作。
对于tomcat的崇敬之情让我决定深入其中,一探究竟。于是我找到了网上大家首推的教材——《深入剖析tomcat》,书比较老,但是很权威,不影响原理性东西的理解。目前已经看到第二章。
读过或者了解该书的应该都知道,这不是一本上来就直接告诉你tomcat的设计思想,用到的什么设计模式或者源码中某一行有什么匠心独运的地方。该书采用一个循序渐进的方式从一个简单的不能再简单的servlet容器开始,之后慢慢丰富,添加功能模块,最终形成我们想知道的tomcat的模样。
背景知识:
- HTTP请求:
请求方法——统一资源标识符URI——协议/版本
请求头
实体

比如这里我们可以看到请求的方法Request Method是GET, Request URL为http://tech.qq.com/a/20160604/007535.htm,并且分别有Request和下面要讲的Response的请求头信息,如Content-Type等。
- HTTP共支持7中请求方法
GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE
- HTTP响应
协议——状态码——描述
响应头
响应实体段
- Socket
socket在应用程序中用于从网络中读取数据,实现不同计算机之间的通讯,实现一个socket需要知道对应应用程序的ip地址和端口号。
首先创建该socket类的实例,有了该实例后,就可以使用它实现发送或接收字节流。如果想要发送字节流,需要调用socket类的getOutputStream来获取一个java.io.OutputStream对象;要发送文本到远程应用程序,需要使用返回的OutputStream对象创建一个java.io.PrintWriter对象;要从连接的另一端接收字节流,需要调用Socket类的getInputStream方法,其会返回一个java.io.InputStream对象。
- ServerSocket类
有了客户端的socket可以发送请求,如果没有服务端来响应,发送的请求也是肉包子打狗,ServerSocket就是充当服务端的角色,serversocket出于随时待命的状态,一旦有客户端发出请求,serversocket就要给出响应,从而实现与客户端的通信。
请求响应模型
有了以上的背景知识,我们就可以实现一个简单到爆的通讯模型,新建一个socket客户端通讯类,用于发送和接收数据,还需要创建一个服务端的ServerSocket用于监听和响应客户端的请求。
主要包含以下三个类:
HttpServer:模拟一个Web服务器
package myTest; import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File; public class HttpServer { /** WEB_ROOT is the directory where our HTML and other files reside.
* For this package, WEB_ROOT is the "webroot" directory under the working
* directory.
* The working directory is the location in the file system
* from where the java command was invoked.
*/
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot"; // shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; // the shutdown command received
private boolean shutdown = false; public static void main(String[] args) {
HttpServer server = new HttpServer();
server.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);
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;
}
}
}
}
从代码可以看出:
- 类中分别创建了socket和serversocket;
- 定义了一个WEB_ROOT目录,其中存放了对应请求的相应结果文件;
- 定义了一个关闭命令,通过在浏览器中输入类似.../SHUTDOWN来关闭Web服务器
- 创建了一个await方法,一直监听127.0.0.1的8080端口,如果有请求产生(比如在浏览器中输入一个请求地址),则会进入await方法并执行serverSocket的accept方法。
Request类:
模拟一个HTTP请求。
package myTest; import java.io.InputStream;
import java.io.IOException; public class Request { private InputStream input;
private String uri; public Request(InputStream input) {
this.input = input;
} 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());
} 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;
} public String getUri() {
return uri;
} }
从代码中可以发现:
- 可以实现传递InputStream对象,在处理与客户端通讯的Socket对象中获取;
- 调用InputStream对象的read来获取HTTP请求的原始数据;
- parse方法用于解析HTTP请求中的原始数据(原始数据由上面的getInputStream中获得);
- parseUri作为一个私有方法被parse调用,用于解析HTTP请求的URI
Response类:
模拟HTTP的响应。
package myTest; import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File; /*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/ public class Response { private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output; public Response(OutputStream output) {
this.output = output;
} public void setRequest(Request request) {
this.request = request;
} 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();
}
}
}
从代码可以发现:
- 和Request类似,这里通过接受OutputStream构造了Response对象;
- setRequest方法用于接收Request对象,因为在Response中需要用到Request的getUri方法;
- sendStaticResource方法主要用于处理请求的响应,如这里发送一个静态资源html作为请求的结果
至此, 本篇主要提到:
- 一些基本概念如http请求、http响应、socket等;
- 对于一个超级简陋的web服务器有了基本的认识;
- 明确了客户端和服务端各自的角色和职责。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
友情赞助
如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。
1. 支付宝 2. 微信

探秘Tomcat——从一个简陋的Web服务器开始的更多相关文章
- 使用java基础实现一个简陋的web服务器软件
使用java基础实现一个简陋的web服务器软件 1.写在前面 大学已经过了一年半了,从接触各种web服务器软件已经有一年多了,从大一上最开始折腾Windows电脑自带的IIS开始,上手了自己的第一个静 ...
- Tomcat剖析(一):一个简单的Web服务器
Tomcat剖析(一):一个简单的Web服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器 ...
- 《深度解析Tomcat》 第一章 一个简单的Web服务器
本章介绍Java Web服务器是如何运行的.从中可以知道Tomcat是如何工作的. 基于Java的Web服务器会使用java.net.Socket类和java.net.ServerSocket类这两个 ...
- Tomcat源码学习记录--web服务器初步认识
Tomcat作为开源的轻量级WEB服务器,虽然不是很适合某些大型项目,但是它开源,读其源代码可以很好的提高我们的编程功底和设计思维.Tomcat中用到了很多比较好的设计模式,其中代码风格也很值得我们去 ...
- 自己模拟的一个简单的web服务器
首先我为大家推荐一本书:How Tomcat Works.这本书讲的很详细的,虽然实际开发中我们并不会自己去写一个tomcat,但是对于了解Tomcat是如何工作的还是很有必要的. Servlet容器 ...
- 一个简单的Web服务器-支持Servlet请求
上接 一个简单的Web服务器-支持静态资源请求,这个服务器可以处理静态资源的请求,那么如何处理Servlet请求的呢? 判断是否是Servlet请求 首先Web服务器需要判断当前请求是否是Servle ...
- 一个简单的Web服务器-支持静态资源请求
目标 实现一个简单的Web服务器,能够根据HTTP请求的URL响应对应的静态资源,如果静态资源不存在则响应404. HttpServer 使用ServerSocket实现的一个服务器,request根 ...
- 自己动手模拟开发一个简单的Web服务器
开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...
- 一个简单的web服务器
写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...
随机推荐
- Hibernate多对多双向关联的配置
Hibernate的双向多对多关联有两种配置方法:那我们就来看看两种方案是如何配置的. 一.创建以各自类为类型的集合来关联 1.首先我们要在两个实体类(雇员<Emploee>.工程< ...
- mysql解决其他服务器不可连接问题
在安装mysql的机器上运行: 1.d:\mysql\bin\>mysql -h localhost -u root //这样应该可以进入MySQL服务器 2.mysql> ...
- 浅谈Js原型的理解
一.js中的原型毫无疑问一个难点,学习如果不深入很容易就晕了! 在参考了多方面的资料后,发现解释都太过专业,对于很多还没有接触过面向对象 语言的小白来说,有理解不了里面的专有名词!如果你没 ...
- CYQ.Data V5 数据库读写分离功能介绍
前言 好多年没写关于此框架的新功能的介绍了,这些年一直在默默地更新,从Nuget上的记录就可以看出来: 这几天在看Java的一些东西,除了觉的Java和.NET的相似度实在太高之外,就是Java太原始 ...
- 开源消息队列:NetMQ
NetMQ 是 ZeroMQ的C#移植版本. ZeroMQ是一个轻量级的消息内核,它是对标准socket接口的扩展.它提供了一种异步消息队列,多消息模式,消息过滤(订阅),对多种传输协议的无缝访问. ...
- node基本理念(事件、多线程、进程)
关于node的几点理解: 1.所谓的node单线程,是指node的event loop,也就是我们的业务脚本是跑在一个单线程中的,可以理解为主线程: 2.若存在IO操作,例如文件.网络,是单独在线程里 ...
- Nova PhoneGap框架 第七章 设备事件处理
我们的框架包含了几种设备事件的处理,目的是为了让我们的程序员更容易的完成代码.这些事件包括:回退键(Android)和横竖屏切换事件. 7.1 Android回退键 首先来说说回退键的事件处理.当用户 ...
- HTTPS那些事(三)攻击实例与防御(转载)
原创地址:http://www.guokr.com/blog/148613/ 在<HTTPS那些事(二)SSL证书>我描述了使用SSL证书时一些需要注意的安全问题,在这一篇文章里面我再 ...
- KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器
无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...
- 判断手机端用户打开页面时是android还是ios,并将判断结果通过ajax返回给url接口,传递回去
首先判断页面是android还是ios,然后利用ajax将结果通过接口url返回回去,记录到log日志中,以统计android和ios用户访问该页面的数量(数据统计) <script type= ...