亚洲国产日韩人妖另类,久久只有这里有精品热久久,依依成人精品视频在线观看,免费国产午夜视频在线

      
      

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

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

        寫在前面

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

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

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

        基本結(jié)構(gòu)

        tomcat架構(gòu)圖

        我們可以把上面這張架構(gòu)圖做簡化,簡化后為:

        什么是http協(xié)議

        Http是一種網(wǎng)絡應用層協(xié)議,規(guī)定了瀏覽器與web服務器之間如何通信以及數(shù)據(jù)包的結(jié)構(gòu)。

        通信大致可以分為四步:

      1. 先建立連接。
      2. 發(fā)送請求數(shù)據(jù)包。
      3. 發(fā)送響應數(shù)據(jù)包。
      4. 關(guān)閉連接。
      5. 優(yōu)點

        web服務器可以利用有限的連接為盡可能多的客戶請求服務。

        tomcat中Servlet的運作方式

      6. 在瀏覽器地址欄輸入http://ip:port/servlet-day01/hello
      7. 瀏覽器依據(jù)IP、port建立連接(即與web服務器之間建立網(wǎng)絡連接)。
      8. 瀏覽器需要將相關(guān)數(shù)據(jù)打包(即按照http協(xié)議要求,制作一個 請求數(shù)據(jù)包,包含了一些數(shù)據(jù),比如請求資源路徑),并且將請求 數(shù)據(jù)包發(fā)送出去。
      9. web服務器會將請求數(shù)據(jù)包中數(shù)據(jù)解析出來,并且將這些數(shù)據(jù)添加 到request對象,同時,還會創(chuàng)建一個response對象。
      10. web服務器創(chuàng)建Servlet對象,然后調(diào)用該對象的service方法(會將request和response作為參數(shù))。注:在service方法里面,通過使用request獲得請求相關(guān)的數(shù)據(jù), 比如請求參數(shù)值,然后將處理結(jié)果寫到response。
      11. web服務器將response中的數(shù)據(jù)取出來,制作響應數(shù)據(jù)包,然后發(fā)送給瀏覽器。
      12. 瀏覽器解析響應數(shù)據(jù)包,然后展現(xiàn)。
      13. 可以總結(jié)唯一張圖:

        什么是Servlet呢?

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

        什么是Servlet規(guī)范?

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

        第一版:Socket版

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

        服務端代碼如下:

        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 = “”; 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(“服務端已經(jīng)準備好了”); 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 = {127, 0, 0, 1}; InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp); //首先直接創(chuàng)建socket,端口號1~1023為系統(tǒng)保存,一般設在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); } }}

        過程如下:

        ,時長00:44

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

        實現(xiàn)代碼如下:

        package com.tian.v2;import java.io.IOException;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;public class MyTomcat { /** * 設定啟動和監(jiān)聽端口 */ private int port = 8090; /** * 啟動函數(shù) * * @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(); }}

        再寫一個工具類,內(nèi)容如下;

        ackage com.tian.v2;public class HttpProtocolUtil { /** * 200 狀態(tài)碼,頭信息 * * @param contentLength 響應信息長度 * @return 200 header info */ public static String getHttpHeader200(long contentLength) { return “HTTP/1.1 200 OK ” + “Content-Type: text/html ” + “Content-Length: ” + contentLength + ” ” + “r”; } /** * 為響應碼 404 提供請求頭信息(此處也包含了數(shù)據(jù)內(nèi)容) * * @return 404 header info */ public static String getHttpHeader404() { String str404 = “

        404 not found

        “; return “HTTP/1.1 404 NOT Found ” + “Content-Type: text/html ” + “Content-Length: ” + str404.getBytes().length + ” ” + “r” + str404; }}

        啟動main方法:

        使用IDEA訪問:

        在瀏覽器訪問:

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

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

        一個http協(xié)議的請求包含三部分:

        • 方法 URI 協(xié)議/版本
        • 請求的頭部
        • 主體內(nèi)容

        比如

        POST /index.html HTTP/1.1Accept: text/plain; text/htmlAccept-Language: en-gbConnection: Keep-AliveHost: localhostUser-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)Content-Length: 33Content-Type: application/x-www-form-urlencodedAccept-Encoding: gzip, deflatelastName=tian&firstName=JohnTian

        簡單的解釋

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

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

        • 協(xié)議 狀態(tài) 狀態(tài)描述
        • 響應的頭部
        • 主體內(nèi)容

        比如:

        HTTP/1.1 200 OKServer: Microsoft-IIS/4.0Date: Mon, 5 Jan 2004 13:13:33 GMTContent-Type: text/htmlLast-Modified: Mon, 5 Jan 2004 13:13:12 GMTContent-Length: 112HTTP Response Example Welcome to Brainy Software

        簡單解釋

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

        代碼實現(xiàn)

        創(chuàng)建一個工具類,用來獲取靜態(tài)資源信息。

        package com.tian.v3;import com.tian.v2.HttpProtocolUtil;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;/** * 提取了一些共用類和函數(shù) */public class ResourceUtil { /** * 根據(jù)請求 url 獲取完整絕對路徑 */ public static String getPath(String url) { String path = ResourceUtil.class.getResource(“/”).getPath(); return path.replaceAll(“”, “/”) + url; } /** * 輸出靜態(tài)資源信息 */ 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) { byteSize = (int) (resourceSize – written); bytes = new byte[byteSize]; } input.read(bytes); output.write(bytes); output.flush(); written += byteSize; } }}

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

        再創(chuàng)建Request類,用來解析并存放請求相關(guān)參數(shù)。

        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; /** * 請求信息輸入流 * 示例 * * 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 * */ 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”); // 讀取第一行數(shù)據(jù),即:GET / HTTP/1.1 String firstLine = requestStringArray[0]; // 遍歷第一行數(shù)據(jù)按空格分隔 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進行小小調(diào)整:

        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); // 根據(jù) request 中的 url,輸出 response.outputHtml(request.getUrl()); accept.close(); } } public static void main(String[] args) throws IOException { MyTomcat tomcat = new MyTomcat(); tomcat.start(); }}

        然后再創(chuàng)建一個index.html,內(nèi)容很簡單:

        hello world

        you already succeed!

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

        啟動MyTomcat。

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

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

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

        第四版:實現(xiàn)動態(tài)請求資源

        用過servlet的同學都知道,Servlet中有三個很重要的方法init、destroy 、service 。其中還記得我們自己寫LoginServlet的時候,還會重寫HttpServlet中的doGet()和doPost()方法。下面?zhèn)兙妥约簛砀阋粋€:

        Servlet類代碼如下:

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

        然后再寫一個HttpServlet來實現(xiàn)Servlet。

        代碼實現(xiàn)如下:

        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 = “

        LoginServlet by GET method

        “; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void doPost(Request request, Response response) throws Exception { String repText = “

        LoginServlet by POST method

        “; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void init() throws Exception { } @Override public void destroy() throws Exception { }}

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

        login com.tian.v4.LoginServlet login /login

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

        dom4j dom4j 1.6.1 jaxen jaxen 1.1.6

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

        下面有個很重要的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 { /** * 設定啟動和監(jiān)聽端口 */ private static final int PORT = 8090; /** * 存放 Servlet信息,url: Servlet 實例 */ private Map 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); // 根據(jù) url 來獲取 Servlet HttpServlet httpServlet = servletMap.get(requestV3.getUrl()); // 如果 Servlet 為空,說明是靜態(tài)資源,不為空即為動態(tài)資源,需要執(zhí)行 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 list = rootElement.selectNodes(“//servlet”); for (Element element : list) { // show Element servletnameElement = (Element) element.selectSingleNode(“servlet-name”); String servletName = servletnameElement.getStringValue(); // server.ShowServlet Element servletclassElement = (Element) element.selectSingleNode(“servlet-class”); String servletClass = servletclassElement.getStringValue(); // 根據(jù) 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:同步并阻塞,服務器實現(xiàn)模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。

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

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

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

        總結(jié)

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

        比如:

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

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

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


        鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
        上一篇 2022年7月11日 09:34
        下一篇 2022年7月11日 09:34

        相關(guān)推薦

        聯(lián)系我們

        聯(lián)系郵箱:admin#wlmqw.com
        工作時間:周一至周五,10:30-18:30,節(jié)假日休息