手写一个简化版Tomcat

1、Tomcat工做原理

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

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

 

   这个类有两个做用 :1.初始化一个守护进程变量、加载类和相应参数。2.解析命令,并执行。web

       源码不过多赘述,咱们在这里只须要把握总体架构,有兴趣的同窗能够本身研究下源码。Tomcat的server.xml配置文件中能够对应构架图中位置,多层的表示能够配置多个:数组

 

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

  • Server:服务器Tomcat的顶级元素,它包含了全部东西。服务器

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

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

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

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

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

  • Context(上下文 ): 即 Web 应用程序一个 Context 即对于一个 Web 应用程序。Context容器直接管理Servlet的运行,Servlet会被其给包装成一个StandardWrapper类去运行。Wrapper负责管理一个Servlet的装载、初始化、执行以及资源回收,它是最底层容器。

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

 

2、梳理本身的Tomcat实现思路

一个请求要请求服务器端的一个文件,服务端根据路径查找该文件,若是有则读取给文件并把文件内容响应回客户端。    

实现以上效果总体思路以下:

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

      2.拿到浏览器的请求,解析并返回URL地址,用I/O输入流读取本地磁盘上相应文件。

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

3、实现本身的Tomcat

工程文件结构:

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

package com.jp.jpHttp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * HttpServer核心处理类,用于接受用户请求,传递HTTP请求头信息,关闭容器
 * 
 */
public class HttpServer {
    // 用于判断是否须要关闭容器
    private boolean shutdown = false;

    public void acceptWait() {
        ServerSocket serverSocket = null;
        try {
            /**
             * serverSocket的三个参数
             * TCP端口号:0-65535,端口号 0 在全部空闲端口上建立套接字
             * 最大链接数:传入链接指示(对链接的请求)的最大队列长度被设置为 backlog 参数。若是队列满时收到链接指示,则拒绝该链接。
             * ip地址: 参数能够在 ServerSocket 的多宿主主机 (multi-homed host) 上使用,ServerSocket 仅接受对其地址之一的链接请求。若是 bindAddr 为 null,则默认接受任何/全部本地地址上的链接
             */
            serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        // 等待用户发请求
        while (!shutdown) {
            try {
                Socket socket = serverSocket.accept();
                InputStream is = socket.getInputStream();
                OutputStream os = socket.getOutputStream();
                
                // 接受请求参数
                Request request = new Request(is);
                request.parse();
                
                // 建立用于返回浏览器的对象
                Response response = new Response(os);
                response.setRequest(request);
                response.sendStaticResource();
                
                // 关闭一次请求的socket,由于http请求就是采用短链接的方式
                socket.close();
                System.out.println("服务端的serverSocket关闭");
                // 若是请求地址是/shutdown 则关闭容器
                if (null != request) {
                    shutdown = request.getUrL().equals("/shutdown");
                }
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.acceptWait();
    }
}

2.建立Request类,获取HTTP的请求头全部信息并截取URL地址返回:

 1 package com.jp.jpHttp;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 
 6 /**
 7  * 建立Request类,获取HTTP的请求头全部信息并截取URL地址返回
 8  *
 9  */
10 public class Request {
11     private InputStream is;
12     private String url;
13 
14     public Request(InputStream input) {
15         this.is = input;
16     }
17 
18     public void parse() {
19         // 从socket中读取一个2048长度字符
20         StringBuffer request = new StringBuffer(Response.BUFFER_SIZE);
21         int i;
22         byte[] buffer = new byte[Response.BUFFER_SIZE];
23         try {
24             i = is.read(buffer);//从输入流is读取必定数量的字节,并存储到buffer字节数组中
25         } catch (IOException e) {
26             e.printStackTrace();
27             i = -1;
28         }
29         for (int j = 0; j < i; j++) {
30             request.append((char) buffer[j]);//把buffer字符数组中的字节拼成字符串 request
31         }
32         // 打印读取的socket中的内容
33         System.out.println("打印socket的输入流中的内容");
34         System.out.print(request.toString());//打印字符串request,就是socket里的输入流,即来自客户端的内容
35         url = parseUrL(request.toString()); //从字符串request中抽取出请求路径
36     }
37 
38     //从字符串request中抽取出请求路径的函数
39     private String parseUrL(String requestString) {
40         int index1, index2;
41         index1 = requestString.indexOf(' ');// 看socket获取请求头是否有值
42         if (index1 != -1) {
43             index2 = requestString.indexOf(' ', index1 + 1);
44             if (index2 > index1)
45                 System.out.println();
46                 System.out.println("获取到请求文件的路径(url)" + requestString.substring(index1 + 1, index2));
47                 return requestString.substring(index1 + 1, index2);
48         }
49         return null;
50     }
51 
52     public String getUrL() {
53         return url;
54     }
55 
56 }

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

 1 package com.jp.jpHttp;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.IOException;
 6 import java.io.OutputStream;
 7 
 8 /**
 9  * 建立Response类,响应请求读取文件并写回到浏览器
10  */
11 public class Response {
12     public static final int BUFFER_SIZE = 2048;
13     // 浏览器访问D盘的文件
14     private static final String WEB_ROOT = "D:";
15     private Request request;
16     private OutputStream output;
17 
18     public Response(OutputStream output) {
19         this.output = output;
20     }
21 
22     public void setRequest(Request request) {
23         this.request = request;
24     }
25 
26     public void sendStaticResource() throws IOException {
27         byte[] bytes = new byte[BUFFER_SIZE];
28         FileInputStream fis = null;
29         try {
30             // 拼接本地目录和浏览器端口号后面的目录
31             File file = new File(WEB_ROOT, request.getUrL());
32             System.out.println("请求路径拼接为服务器内的文件路径:"+ file.getAbsolutePath());
33             //System.out.println("测试应用程序是否能够读取此抽象路径名表示的文件:" + file.canRead());
34             // 若是文件存在,且不是个目录
35             if (file.exists() && !file.isDirectory()) {
36                 fis = new FileInputStream(file);
37                 int ch = fis.read(bytes, 0, BUFFER_SIZE);
38                 while (ch != -1) {
39                     output.write(bytes, 0, ch);
40                     ch = fis.read(bytes, 0, BUFFER_SIZE);
41                 }
42                 System.out.println("请求的文件存在,正在把文件做为响应写进socket的输出流");
43             } else {
44                 // 文件不存在,返回给浏览器响应提示,这里能够拼接HTML任何元素
45                 String retMessage = "<h1>" + file.getName() + " file or directory not exists</h1>";
46                 String returnMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n"
47                         + "Content-Length: " + retMessage.length() + "\r\n" + "\r\n" + retMessage;
48                 output.write(returnMessage.getBytes());
49                 System.out.println("请求的文件不存在,返回404");
50             }
51         } catch (Exception e) {
52             System.out.println(e.toString());
53         } finally {
54             if (fis != null)
55                 fis.close();
56         }
57     }
58 }

实验文件

实验结果

 

 

请求文件不存在

 

当请求http://localhost:8080/shutdown 时,关闭容器,即再也不监听端口

4、读者能够本身作的优化,扩展的点

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

      2.抽象Servlet类来转码处理请求和响应的业务。发过来的请求会有不少,也就意味着咱们应该会有不少的Servlet,例如:RegisterServlet、LoginServlet等等还有不少其余的访问。能够用到相似于工厂模式的方法处理,随时产生不少的Servlet,来知足不一样的功能性的请求。

      3.使用多线程技术。本文的代码是死循环,且只能有一个连接,而现实中的状况是每每会有不少不少的客户端发请求,能够把每一个浏览器的通讯封装到一个线程当中。

 

https://my.oschina.net/liughDevelop/blog/1790893

相关文章
相关标签/搜索