转载

你真的了解Tomcat吗之简单搭建一个Tomcat

  1. 接受来自客户端的请求并根据请求流中的信息生成相应的Request对象和用于返回的Response对象;(如请求所使用的协议,HTTP?AJP?;请求资源的URL?等信息放到Request对象中)
  2. 调用容器中与URL相对应的处理程序处理该请求;
  3. 通过Response对象将服务器处理的结果返回至客户端。

二、架构发展过程:

2.1 原始的刀耕火种时代:

使用Socket与ServerSocket来构建客户端与服务器通信的桥梁,那个时候的架构是这样的:

你真的了解Tomcat吗之简单搭建一个Tomcat

Server端负责既负责与客户端连接又负责处理请求并返回处理结果;

下面是一个简单的Demo,麻雀虽小,五脏俱全,简单实现了Tomcat的主要功能:

2.1.1 这是通信客户端,主要功能是向服务端发起一个HTTP请求:

"GET /index.html HTTP/1.1 Host: localhost:8080 Connection:Close"意为请求服务器ContextPath下相对路径为 "/index.html" 的这个资源:

/**
 * @CreatedBy:成江灿
 * @Date:2019/12/22/15:24
 * @Description:Socket通信客户端
 */
public class Client {
    public static void main(String[] args) {
       Socket socket = new Socket("127.0.0.1", 8080){
            OutputStream os = socket.getOutputStream();
            PrintWriter out = new PrintWriter(os, true);
            /*向服务器发送一个HTTP1.1的请求*/
            out.println("GET /index.html HTTP/1.1");
            out.println("Host: localhost:8080");
            out.println("Connection Close");
            out.println();
            /*读取服务器端响应的信息,这里用了带缓冲的Reader*/
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer stringBuffer = new StringBuffer(8096);
            //一直等待服务器端的响应(NIO通过事件机制和Selector解决了这个问题)
            boolean loop = true;
            while(loop){
                if(in.ready()){
                    int i = 0;
                    //输入流读完后返回的值为-1
                    while(i != -1){
                        i = in.read();
                        stringBuffer.append((char)i);
                    }
                    loop = false;
                }
                Thread.currentThread().sleep(50L);
            }
        }
    }
}
复制代码

2.1.2 这是简单实现的服务器

服务器端的主要作用是根据请求流中的信息生成相应的Request对象,然后根据Request对象拿到URL信息并通过Response将对应URL的资源写回给客户端; ps:因为传统的JavaIO会卡在accept()方法下,为防止线程负荷增高,所以这里使用了线程池。

/**
 * @Description:Socket通信服务器端
 */
public class Server {
    public static final String WEB_ROOT =
            System.getProperty("user.dir") + File.separator  + "webroot";
    /*用于标识服务器是否正在运行*/
    private static boolean shutdown = false;
    private static BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
    private static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 100L, TimeUnit.MINUTES, workQueue);
    public static void main(String[] args) {
        ServerSocket serverSocket81 = null;
            final ServerSocket serverSocket80 =  new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
            Runnable runnableTask80 = new Runnable() {
                @Override
                public void run() {
                    while(!shutdown){
                        Socket socket = null;
                        InputStream inputStream = null;
                        OutputStream outputStream = null;
                        /*serverSocket建立连接后使用socket传值*/
                        socket = serverSocket80.accept();
                        inputStream = socket.getInputStream();
                        /*根据传入的请求流构建Request对象*/
                        Request request = new Request(inputStream);
                        request.parse();
                        /*创建Response对象用于返回*/
                        Response response = new Response(outputStream);
                        response.setRequest(request);
                        response.sendStaticResource();
                        /*关闭Socket*/
                        socket.close();
                    }
                }
            };
            executorService.execute(runnableTask80);
    }
}
复制代码

2.1.3 这是简单实现的Request

主要负责从请求流中获取URL;

/**
 * @CreatedBy:成江灿
 * @Date:2019/12/22/16:17
 * @Description:将Socket中传入的请求流转为请求对象
 */
public class Request {
    private InputStream inputStream;
    private String uri;

    public Request(InputStream inputStream){
        this.inputStream = inputStream;
    }

    public void parse(){
        /*从输入流中获取请求信息*/
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try{
            /*将输入流写入到缓冲区*/
            i = inputStream.read(buffer);
        }
        catch (IOException e){
            e.printStackTrace();
            i = -1;
        }
        for(int j=0; j<i; j++){
            request.append((char)buffer[j]);
        }
        uri = parseUri(request.toString());
    }

    private String parseUri(String requestString){
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            /*取出HTTP请求的第一行中涉及到URL的第二部分*/
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri(){
        return uri;
    }
}
复制代码

2.1.4 这是简单实现的返回对象

/**
 * @CreatedBy:成江灿
 * @Date:2019/12/22/16:28
 * @Description:主要作用是将请求的文件返回
 */
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(System.getProperty("user.dir") + File.separator  + "webroot", 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();
        }
    }
}
复制代码

2.2 各种协议并发的年代:

可以注意到,上面编写的服务端只能接受HTTP协议的请求并相应结果,而且上述程序是高度耦合的, 即连接的建立与请求的处理是耦合在服务器端的 ,所以一旦协议发生改变,那么连接的规则也会发生改变,那么服务器的代码也要发生改变,也许你会觉得重新复制一遍代码新建一个服务器端程序不就好了吗? 但你是否想过也许他们的请求处理程序可以是一样的吗,即处理程序它只是通过Request对象来获取信息的,所以我们是不是只需要改变一下处理请求流的规则便可以了呢?

所以我们需要将请求的连接与请求的处理在服务器端解耦合!!新版的架构图如下:

你真的了解Tomcat吗之简单搭建一个Tomcat

其中:(1)Connector负责协议负责各种协议连接的建立和Request和Response对象的构建;

(2)Engine则负责通过Request和Response对象对请求进行处理;

(3)Service则用于管理多个Connector与其相对应的Engine,因为基于不同的协议的请求可能会共用同一套处理程序的嘛!

三、结尾:

写一篇博客真的要好久,写出来自己还是感觉很多地方没有说清,下次再开另一篇讲讲Engine吧~

原文  https://juejin.im/post/5dfed113f265da33c90b4c5c
正文到此结束
Loading...