Tomcat工作原理及简单模拟实现

Posted Java中文社群

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat工作原理及简单模拟实现相关的知识,希望对你有一定的参考价值。

Tomcat应该都不陌生,我们经常会把写好的代码打包放在Tomcat里并启动,然后在浏览器里就能愉快的调用我们写的代码来实现相应的功能了,那么Tomcat是如何工作的?

一、Tomcat工作原理

我们启动Tomcat时双击的startup.bat文件的主要作用是找到catalina.bat,并且把参数传递给它,而catalina.bat中有这样一段话:

Tomcat工作原理及简单模拟实现

Bootstrap.class是整个Tomcat 的入口,我们在Tomcat源码里找到这个类,其中就有我们经常使用的main方法:

Tomcat工作原理及简单模拟实现

这个类有两个作用 :

  • 初始化一个守护进程变量、加载类和相应参数;

  • 解析命令,并执行。

源码不过多赘述,我们在这里只需要把握整体架构,有兴趣的同学可以自己研究下源码。Tomcat的server.xml配置文件中可以对应构架图中位置,多层的表示可以配置多个:

Tomcat工作原理及简单模拟实现

即一个由 Server->Service->Engine->Host->Context 组成的结构,从里层向外层分别是:

Server:服务器Tomcat的顶级元素,它包含了所有东西。

Service:一组 Engine(引擎) 的集合,包括线程池 Executor 和连接器 Connector 的定义。

Engine(引擎):一个 Engine代表一个完整的 Servlet引擎,它接收来自Connector的请求,并决定传给哪个Host来处理。

Container(容器):Host、Context、Engine和Wraper都继承自Container接口,它们都是容器。

Connector(连接器):将Service和Container连接起来,注册到一个Service,把来自客户端的请求转发到Container。

Host:即虚拟主机,所谓的”一个虚拟主机”可简单理解为”一个网站”。

Context(上下文 ): 即 Web 应用程序,一个 Context 即对于一个 Web 应用程序。

Context容器直接管理Servlet的运行,Servlet会被其给包装成一个StandardWrapper类去运行。Wrapper负责管理一个Servlet的装载、初始化、执行以及资源回收,它是最底层容器。

比如现在有以下网址,根据“/”切割的链接就会定位到具体的处理逻辑上,且每个容器都有过滤功能。

Tomcat工作原理及简单模拟实现

二、Tomcat实现思路

Tomcat工作原理及简单模拟实现

实现以上效果整体思路如下:

1.ServerSocket占用8080端口,用while(true)循环等待用户发请求。

3.读取文件,不存在构建响应报文头、html正文内容,存在则写到浏览器端。

三、实现Tomcat

工程文件结构和pom.xml文件:

1.HttpServer核心处理类,用于接受用户请求,传递HTTP请求头信息,关闭容器:

 
   
   
 
  1. public class HttpServer {

  2. // 用于判断是否需要关闭容器

  3. private boolean shutdown = false;

  4. public void acceptWait() {

  5. ServerSocket serverSocket = null;

  6. try {

  7. serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

  8. }

  9. catch (IOException e) {

  10. e.printStackTrace();

  11. System.exit(1);

  12. }

  13. // 等待用户发请求

  14. while (!shutdown) {

  15. try {

  16. Socket socket = serverSocket.accept();

  17. InputStream is = socket.getInputStream();

  18. OutputStream os = socket.getOutputStream();

  19. // 接受请求参数

  20. Request request = new Request(is);

  21. request.parse();

  22. // 创建用于返回浏览器的对象

  23. Response response = new Response(os);

  24. response.setRequest(request);

  25. response.sendStaticResource();

  26. //关闭一次请求的socket,因为http请求就是采用短连接的方式

  27. socket.close();

  28. if(null != request){

  29. shutdown = request.getUrL().equals("/shutdown");

  30. }

  31. }

  32. catch (Exception e) {

  33. e.printStackTrace();

  34. continue;

  35. }

  36. }

  37. }

  38. public static void main(String[] args) {

  39. HttpServer server = new HttpServer();

  40. server.acceptWait();

  41. }

  42. }

 
   
   
 
  1. public class Request {

  2. private InputStream is;

  3. private String url;

  4. public Request(InputStream input) {

  5. this.is = input;

  6. }

  7. public void parse() {

  8. //从socket中读取一个2048长度字符

  9. StringBuffer request = new StringBuffer(Response.BUFFER_SIZE);

  10. int i;

  11. byte[] buffer = new byte[Response.BUFFER_SIZE];

  12. try {

  13. i = is.read(buffer);

  14. }

  15. catch (IOException e) {

  16. e.printStackTrace();

  17. i = -1;

  18. }

  19. for (int j=0; j<i; j++) {

  20. request.append((char) buffer[j]);

  21. }

  22. //打印读取的socket中的内容

  23. System.out.print(request.toString());

  24. url = parseUrL(request.toString());

  25. }

  26. private String parseUrL(String requestString) {

  27. int index1, index2;

  28. index1 = requestString.indexOf(' ');//看socket获取请求头是否有值

  29. if (index1 != -1) {

  30. index2 = requestString.indexOf(' ', index1 + 1);

  31. if (index2 > index1)

  32. return requestString.substring(index1 + 1, index2);

  33. }

  34. return null;

  35. }

  36. public String getUrL() {

  37. return url;

  38. }

  39. }

3.创建Response类,响应请求读取文件并写回到浏览器

 
   
   
 
  1. public class Response {

  2. public static final int BUFFER_SIZE = 2048;

  3. //浏览器访问D盘的文件

  4. private static final String WEB_ROOT ="D:";

  5. private Request request;

  6. private OutputStream output;

  7. public Response(OutputStream output) {

  8. this.output = output;

  9. }

  10. public void setRequest(Request request) {

  11. this.request = request;

  12. }

  13. public void sendStaticResource() throws IOException {

  14. byte[] bytes = new byte[BUFFER_SIZE];

  15. FileInputStream fis = null;

  16. try {

  17. //拼接本地目录和浏览器端口号后面的目录

  18. File file = new File(WEB_ROOT, request.getUrL());

  19. //如果文件存在,且不是个目录

  20. if (file.exists() && !file.isDirectory()) {

  21. fis = new FileInputStream(file);

  22. int ch = fis.read(bytes, 0, BUFFER_SIZE);

  23. while (ch!=-1) {

  24. output.write(bytes, 0, ch);

  25. ch = fis.read(bytes, 0, BUFFER_SIZE);

  26. }

  27. }else {

  28. //文件不存在,返回给浏览器响应提示,这里可以拼接HTML任何元素

  29. String retMessage = "<h1>"+file.getName()+" file or directory not exists</h1>";

  30. String returnMessage ="HTTP/1.1 404 File Not Found " +

  31. "Content-Type: text/html " +

  32. "Content-Length: "+retMessage.length()+" " +

  33. " " +

  34. retMessage;

  35. output.write(returnMessage.getBytes());

  36. }

  37. }

  38. catch (Exception e) {

  39. System.out.println(e.toString() );

  40. }

  41. finally {

  42. if (fis!=null)

  43. fis.close();

  44. }

  45. }

  46. }

四、扩展点

1.在WEB_INF文件夹下读取web.xml解析,通过请求名找到对应的类名,通过类名创建对象,用反射来初始化配置信息,如welcome页面,Servlet、servlet-mapping,filter,listener,启动加载级别等。

2.抽象Servlet类来转码处理请求和响应的业务。发过来的请求会有很多,也就意味着我们应该会有很多的Servlet,例如:RegisterServlet、LoginServlet等等还有很多其他的访问。可以用到类似于工厂模式的方法处理,随时产生很多的Servlet,来满足不同的功能性的请求。

3.使用多线程。本文的代码是死循环,且只能有一个链接,而现实中的情况是往往会有很多很多的客户端发请求,可以把每个浏览器的通信封装到一个线程当中。

近期热门文章


转发朋友圈,是对我最大的支持。


以上是关于Tomcat工作原理及简单模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

Struts2学习一----------Struts2的工作原理及HelloWorld简单实现

Tomcat性能优化及JVM内存工作原理

Android主流视频播放及缓存实现原理调研

java agent技术原理及简单实现

Spring MVC工作原理及源码解析DispatcherServlet实现原理及源码解析

6张图说清楚Tomcat原理及请求流程