久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    驚呆了!手寫4個mini版的tomcat!

     田維常 2021-03-17

    寫在前面

    Apache Tomcat  是Java Servlet, JavaServer Pages (JSP),Java表達式語言和Java的WebSocket技術的一個開源實現 ,通常我們將Tomcat稱為Web容器或者Servlet容器 。

    今天,我們就來手寫tomcat,但是說明一下:咱們不是為了裝逼才來寫tomcat,而是希望大家能更多的理解和掌握tomcat。

    廢話不多說了,直接開干。

    基本結構

    tomcat架構圖

    我們可以把上面這張架構圖做簡化,簡化后為:

    什么是http協議

    Http是一種網絡應用層協議,規定了瀏覽器與web服務器之間如何通信以及數據包的結構。

    通信大致可以分為四步:

    1. 先建立連接。
    2. 發送請求數據包。
    3. 發送響應數據包。
    4. 關閉連接。

    優點

    web服務器可以利用有限的連接為盡可能多的客戶請求服務。
    tomcat中Servlet的運作方式
    1. 在瀏覽器地址欄輸入http://ip:port/servlet-day01/hello
    2. 瀏覽器依據IP、port建立連接(即與web服務器之間建立網絡連接)。
    3. 瀏覽器需要將相關數據打包(即按照http協議要求,制作一個 請求數據包,包含了一些數據,比如請求資源路徑),并且將請求 數據包發送出去。
    4. web服務器會將請求數據包中數據解析出來,并且將這些數據添加    到request對象,同時,還會創建一個response對象。
    5. web服務器創建Servlet對象,然后調用該對象的service方法(會將request和response作為參數)。注:在service方法里面,通過使用request獲得請求相關的數據, 比如請求參數值,然后將處理結果寫到response。
    6. web服務器將response中的數據取出來,制作響應數據包,然后發送給瀏覽器。
    7. 瀏覽器解析響應數據包,然后展現。

    可以總結唯一張圖:

    什么是Servlet呢?

    Servlet是JavaEE規范的一種,主要是為了擴展Java作為Web服務的功能,統一接口。由其他內部廠商如tomcat,jetty內部實現web的功能。如一個http請求到來:容器將請求封裝為servlet中的HttpServletRequest對象,調用init(),service()等方法輸出response,由容器包裝為httpresponse返回給客戶端的過程。

    什么是Servlet規范?

    • 從 Jar 包上來說,Servlet 規范就是兩個 Jar 文件。servlet-api.jar 和 jsp-api.jar,Jsp 也是一種 Servlet。
    • 從package上來說,就是 javax.servlet 和 javax.servlet.http 兩個包。
    • 從接口來說,就是規范了 Servlet 接口、Filter 接口、Listener 接口、ServletRequest 接口、ServletResponse 接口等。類圖如下:

    第一版:Socket版

    使用Socket編程,實現簡單的客戶端和服務端的聊天。

    服務端代碼如下:

    package com.tian.v1;

    import java.io.*;
    import java.net.*;


    public class Server {

        public static String readline = null;
        public static String inTemp = null;
        public static String turnLine = "\n";
        public static final String client = "客戶端:";
        public static final String server = "服務端:";
        public static final int PORT = 8090;

        public static void main(String[] args) throws Exception {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務端已經準備好了");
            Socket socket = serverSocket.accept();

            BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
            BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
            while (true) {
                inTemp = socketIn.readLine();
                if (inTemp != null &&inTemp.contains("over")) {
                    systemIn.close();
                    socketIn.close();
                    socketOut.close();
                    socket.close();
                    serverSocket.close();
                }
                System.out.println(client + inTemp);
                System.out.print(server);

                readline = systemIn.readLine();

                socketOut.println(readline);
                socketOut.flush();
            }
        }
    }

    客戶端代碼如下:

    package com.tian.v1;

    import java.io.*;
    import java.net.*;

    public class Client {

        public static void main(String[] args) throws Exception {
            String readline;
            String inTemp;
            final String client = "客戶端說:";
            final String server = "服務端回復:";

            int port = 8090;
            byte[] ipAddressTemp = {127001};
            InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp);

            //首先直接創建socket,端口號1~1023為系統保存,一般設在1023之外
            Socket socket = new Socket(ipAddress, port);

            BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
            BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
            while (true) {
                System.out.print(client);
                readline = systemIn.readLine();

                socketOut.println(readline);
                socketOut.flush();
                //處理
                inTemp = socketIn.readLine();
                if (inTemp != null && inTemp.contains("over")) {
                    systemIn.close();
                    socketIn.close();
                    socketOut.close();
                    socket.close();
                }
                System.out.println(server + inTemp);
            }
        }
    }

    過程如下:

    第二版:我們直接請求http://localhost:8090

    實現代碼如下:

    package com.tian.v2;

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

    public class MyTomcat {
        /**
         * 設定啟動和監聽端口
         */

        private int port = 8090;

        /**
         * 啟動函數
         *
         * @throws IOException
         */

        public void start() throws IOException {
            System.out.println("my tomcat starting...");
            String responseData = "6666666";
            ServerSocket socket = new ServerSocket(port);
            while (true) {
                Socket accept = socket.accept();
                OutputStream outputStream = accept.getOutputStream();
                String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData;
                outputStream.write(responseText.getBytes());
                accept.close();
            }
        }

        /**
         * 啟動入口
         */

        public static void main(String[] args) throws IOException {
            MyTomcat tomcat = new MyTomcat();
            tomcat.start();
        }
    }

    再寫一個工具類,內容如下;

    ackage com.tian.v2;

    public class HttpProtocolUtil {

        /**
         * 200 狀態碼,頭信息
         *
         * @param contentLength 響應信息長度
         * @return 200 header info
         */

        public static String getHttpHeader200(long contentLength) {
            return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n"
                    + "Content-Length: " + contentLength + " \n" + "\r\n";
        }

        /**
         * 為響應碼 404 提供請求頭信息(此處也包含了數據內容)
         *
         * @return 404 header info
         */

        public static String getHttpHeader404() {
            String str404 = "<h1>404 not found</h1>";
            return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n"
                    + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404;
        }
    }

    啟動main方法:

    使用IDEA訪問:

    在瀏覽器訪問:

    自此,我們的第二版本搞定。下面繼續第三個版本;

    第三版:封裝請求信息和響應信息

    一個http協議的請求包含三部分:

    • 方法 URI 協議/版本
    • 請求的頭部
    • 主體內容

    比如

    POST /index.html HTTP/1.1
    Accept: text/plain; text/html
    Accept-Language: en-gb
    Connection: Keep-Alive
    Host: localhost
    User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
    Content-Length: 33
    Content-Type: application/x-www-form-urlencoded
    Accept-Encoding: gzip, deflate

    lastName=tian&firstName=JohnTian

    簡單的解釋

    • 數據的第一行包括:方法、URI、協議和版本。在這個例子里,方法為POST,URI為/index.html,協議為HTTP/1.1,協議版本號為1.1。他們之間通過空格來分離。
    • 請求頭部從第二行開始,使用英文冒號(:)來分離鍵和值。
    • 請求頭部和主體內容之間通過空行來分離,例子中的請求體為表單數據。

    類似于http協議的請求,響應也包含三個部分。

    • 協議 狀態 狀態描述
    • 響應的頭部
    • 主體內容

    比如:

    HTTP/1.1 200 OK
    Server: Microsoft-IIS/4.0
    Date: Mon, 5 Jan 2004 13:13:33 GMT
    Content-Type: text/html
    Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
    Content-Length: 112

    <html>
    <head>
    <title>HTTP Response Example</title> </head>
    <body>
    Welcome to Brainy Software
    </body>
    </html>

    簡單解釋

    • 第一行,HTTP/1.1 200 OK表示協議、狀態和狀態描述。
    • 之后表示響應頭部。
    • 響應頭部和主體內容之間使用空行來分離。

    代碼實現

    創建一個工具類,用來獲取靜態資源信息。

    package com.tian.v3;

    import com.tian.v2.HttpProtocolUtil;

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;

    /**
     * 提取了一些共用類和函數
     */

    public class ResourceUtil {

        /**
         * 根據請求 url 獲取完整絕對路徑
         */

        public static String getPath(String url) {
            String path = ResourceUtil.class.getResource("/").getPath();
            return path.replaceAll("\\\\""/") + url;
        }

        /**
         * 輸出靜態資源信息
         */

        public static void outputResource(InputStream input, OutputStream output) throws IOException {
            int count = 0;
            while (count == 0) {
                count = input.available();
            }
            int resourceSize = count;
            output.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
            long written = 0;
            int byteSize = 1024;
            byte[] bytes = new byte[byteSize];
            while (written < resourceSize) {
                if (written + byteSize > resourceSize) {
                    byteSize = (int) (resourceSize - written);
                    bytes = new byte[byteSize];
                }
                input.read(bytes);
                output.write(bytes);
                output.flush();
                written += byteSize;
            }
        }
    }

    另外HttpProtocolUtil照樣用第二版本中。

    再創建Request類,用來解析并存放請求相關參數。

    package com.tian.v3;

    import java.io.IOException;
    import java.io.InputStream;

    public class Request {
        /**
         * 請求方式, eg: GET、POST
         */

        private String method;

        /**
         * 請求路徑,eg: /index.html
         */

        private String url;

        /**
         * 請求信息輸入流 <br>
         * 示例
         * <pre>
         *  GET / HTTP/1.1
         *  Host: localhost
         *  Connection: keep-alive
         *  Pragma: no-cache
         *  Cache-Control: no-cache
         *  Upgrade-Insecure-Requests: 1
         *  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
         * </pre>
         */

        private InputStream inputStream;

        public Request() {
        }

        public Request(InputStream inputStream) throws IOException {
            this.inputStream = inputStream;
            int count = 0;
            while (count == 0) {
                count = inputStream.available();
            }
            byte[] bytes = new byte[count];
            inputStream.read(bytes);
            // requestString 參考:this.inputStream 示例
            String requestString = new String(bytes);
            // 按換行分隔
            String[] requestStringArray = requestString.split("\\n");
            // 讀取第一行數據,即:GET / HTTP/1.1
            String firstLine = requestStringArray[0];
            // 遍歷第一行數據按空格分隔
            String[] firstLineArray = firstLine.split(" ");
            this.method = firstLineArray[0];
            this.url = firstLineArray[1];
        }

        public String getMethod() {
            return method;
        }

        public void setMethod(String method) {
            this.method = method;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public InputStream getInputStream() {
            return inputStream;
        }

        public void setInputStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }
    }

    把第二版的MyTomcat進行小小調整:

    package com.tian.v3;

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

    public class MyTomcat {

        private static final int PORT = 8090;
        public void start() throws IOException {
            System.out.println("my tomcat starting...");
            ServerSocket socket = new ServerSocket(PORT);
            while (true) {
                Socket accept = socket.accept();
                OutputStream outputStream = accept.getOutputStream();
                // 分別封裝 Request 和 Response
                Request request = new Request(accept.getInputStream());
                Response response = new Response(outputStream);
                // 根據 request 中的 url,輸出
                response.outputHtml(request.getUrl());
                accept.close();
            }
        }
       
        public static void main(String[] args) throws IOException {
            MyTomcat tomcat = new MyTomcat();
            tomcat.start();
        }
    }

    然后再創建一個index.html,內容很簡單:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>hello world</title>
    </head>
    <body>
    <h2> you already succeed!</h2>
    </body>
    </html>

    這一需要注意,index.html文件的存放路徑不放錯了,視本地路徑來定哈,放在classes文件夾下的。你可以debug試試,看看你應該放在那個目錄下。

    啟動MyTomcat。

    訪問http://localhost:8090/index.html

    自此,我們針對于Http請求參數和相應參數做了一個簡單的解析以及封裝。

    盡管其中還有很多問題,但是字少看起來有那點像樣了。我們繼續第四版,

    第四版:實現動態請求資源

    用過servlet的同學都知道,Servlet中有三個很重要的方法init、destroy 、service 。其中還記得我們自己寫LoginServlet的時候,還會重寫HttpServlet中的doGet()和doPost()方法。下面們就自己來搞一個:

    Servlet類代碼如下:

    public interface Servlet {
        void init() throws Exception;
        void destroy() throws Exception;
        void service(Request request, Response response) throws Exception;
    }

    然后再寫一個HttpServlet來實現Servlet。

    代碼實現如下:

    package com.tian.v4;

    public abstract class HttpServlet implements Servlet {
        @Override
        public void init() throws Exception {

        }

        @Override
        public void destroy() throws Exception {

        }

        @Override
        public void service(Request request, Response response) throws Exception {
            String method = request.getMethod();
            if ("GET".equalsIgnoreCase(method)) {
                doGet(request, response);
            } else {
                doPost(request, response);
            }
        }
        public abstract void doGet(Request request, Response response) throws Exception;

        public abstract void doPost(Request request, Response response) throws Exception;
    }

    下面我們就來寫一個自己的Servlet,比如LoginServlet。

    package com.tian.v4;

    public class LoginServlet  extends HttpServlet {

        @Override
        public void doGet(Request request, Response response) throws Exception {
            String repText = "<h1> LoginServlet by GET method</h1>";
            response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
        }

        @Override
        public void doPost(Request request, Response response) throws Exception {
            String repText = "<h1>LoginServlet by POST method</h1>";
            response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
        }

        @Override
        public void init() throws Exception {
        }

        @Override
        public void destroy() throws Exception {
        }
    }

    大家是否還記得,我們在學習Servlet的時候,在resources目錄下面有個web.xml。我們這個版本也把這個xml文件給引入。

    <?xml version="1.0" encoding="utf-8"?>
    <web-app>
        <servlet>
            <servlet-name>login</servlet-name>
            <servlet-class>com.tian.v4.LoginServlet</servlet-class>
        </servlet>

        <servlet-mapping>
            <servlet-name>login</servlet-name>
            <url-pattern>/login</url-pattern>
        </servlet-mapping>
    </web-app>

    既然引入了xml文件,那我們就需要去讀取這個xml文件,并解析器內容。所以這里我們需要引入兩個jar包。

    <dependencies>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

    萬事俱備,只欠東風了。這時候我們來吧MyTomcat這個類做一些調整即可。

    下面有個很重要的initServlet()方法,剛剛是對應下面這張圖中的List servlets,但是我們代碼里使用的是Map來存儲Servlet的,意思就那么個意思,把Servlet放在集合里。

    這也就是為什么大家都把Tomcat叫做Servlet容器的原因,其實真正的容器還是java集合。

    package com.tian.v4;

    import com.tian.v3.RequestV3;
    import com.tian.v3.ResponseV3;
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    public class MyTomcat {
        /**
         * 設定啟動和監聽端口
         */

        private static final int PORT = 8090;
        /**
         * 存放 Servlet信息,url: Servlet 實例
         */

        private Map<String, HttpServlet> servletMap = new HashMap<>();
     
        public void start() throws Exception {

            System.out.println("my tomcat starting...");
            initServlet();
            ServerSocket socket = new ServerSocket(PORT);
            while (true) {
                Socket accept = socket.accept();
                OutputStream outputStream = accept.getOutputStream();
                // 分別封裝 RequestV3 和 ResponseV3
                RequestV4 requestV3 = new RequestV4(accept.getInputStream());
                ResponseV4 responseV3 = new ResponseV4(outputStream);
                // 根據 url 來獲取 Servlet
                HttpServlet httpServlet = servletMap.get(requestV3.getUrl());
                // 如果 Servlet 為空,說明是靜態資源,不為空即為動態資源,需要執行 Servlet 里的方法
                if (httpServlet == null) {
                    responseV3.outputHtml(requestV3.getUrl());
                } else {
                    httpServlet.service(requestV3, responseV3);
                }
                accept.close();
            }
        } 
        
        public static void main(String[] args) throws Exception {
            MyTomcat tomcat = new MyTomcat();
            tomcat.start();
        }


        /**
         * 解析web.xml文件,把url和servlet解析出來,
         * 并保存到一個java集合里(Map)
         */

        public void initServlet() throws Exception {
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> list = rootElement.selectNodes("//servlet");
            for (Element element : list) {
                // <servlet-name>show</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.ShowServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();

                // 根據 servlet-name 的值找到 url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /show
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
            }
        }
    }

    啟動,再次訪問http://localhost:8090/index.html

    同時,我們可以訪問http://localhost:8090/login

    到此,第四個版本也搞定了。

    但是前面四個版本都有一個共同的問題,全部使用的是BIO。

    BIO:同步并阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。

    所以,大家在網上看到的手寫tomcat的,也有使用線程池來做的,這里希望大家能get到為什么使用線程池來實現。另外,其實在tomcat高版本中已經沒有使用BIO了。

    而 HTTP/1.1默認使用的就是NIO了。

    但這個只是通信方式,重點是我們要理解和掌握tomcat的整體實現。

    總結

    另外,發現上面都是講配置文件解析,并將對應數據保存起來。熟悉這個套路后,大家是不是想到,我們很多配置項都是在server.xml中,還記得否?也是可以通過解析某個目錄下的server.xml文件,并把內容賦給java中相應的變量罷了。

    比如:

    1.server.xml中的端口配置,我們是在代碼里寫死的而已,改成MyTomcat啟動的時候去解析并獲取不久得了嗎?

    2.我們通常是將我們項目的打成war,然后解壓到某個目錄下,最后還不是可以通過讀取這個解壓后的某個目錄中找到web.xml,然后用回到上面的web.xml解析了。

    本文主要是分享如何從一個塑料版到黃金版、然后鉑金版,最后到磚石版。可以把加入線程池的版本稱之為星耀版,最后把相關server.xml解析,以及讀取我們放入到tomcat中項目解析可以稱之為王者版。

    技術點:Socket編程、InputStream、OutputStream、線程池、xml文件解析、反射。更高級版本中NIO,AIO等。

    不是為了裝逼而來搞這個tomcat,而是為了我們更深刻的理解tomcat的原理。

    參考:http:///Sr5yL

    部分圖片來源網絡,侵刪!

      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 亚洲另类丝袜综合网| 国产老妇伦国产熟女老妇高清| 久久天天躁狠狠躁夜夜AVAPP| 99久久99久久加热有精品| 国产在线中文字幕精品| 精品 日韩 国产 欧美 视频| 亚洲中文字幕久久精品无码喷水| 99久久国产成人免费网站| 亚洲精品午夜国产VA久久成人| 国内极度色诱视频网站| 日韩女同在线二区三区| 四虎永久在线精品免费一区二区| 国产亚洲国产精品二区| 精品日本一区二区三区在线观看 | 亚洲色欲色欱WWW在线| 欧美交A欧美精品喷水| 日韩中文字幕av有码| 50岁熟妇的呻吟声对白| 国产国产午夜福利视频| 亚洲成AV人片在线观看WV| 亚洲精品美女一区二区| 人妻少妇精品无码专区动漫| 亚洲中文字幕日产无码成人片| 亚洲AV永久无码精品主页| 久久精品第九区免费观看| 久久综合九色欧美综合狠狠| 无码毛片一区二区本码视频| 国产精品亚洲二区亚瑟| 亚洲爆乳无码一区二区三区| 亚洲日本高清一区二区三区| 美女无遮挡免费视频网站| 国精品人妻无码一区免费视频电影 | 国产人妻久久精品一区| 黄又色又污又爽又高潮| 精品少妇人妻AV无码久久| 青青青青久久精品国产| 亚洲国产日韩一区三区| 国产欧美一区二区精品久久久| 又黄又硬又湿又刺激视频免费| 色爱综合激情五月激情| 无码日韩精品一区二区三区免费|