目录

源码地址

https://github.com/CoderXiaohui/mini-tomcat

一,分析

Mini版Tomcat需要实现的功能

作为一个服务器软件提供服务(通过浏览器客户端发送Http请求,它可以接收到请求进行处理,处理之后的结果返回浏览器客户端)。

  1. 提供服务,接收请求(socket通信)
  2. 请求信息封装成Request对象,封装响应信息Response对象
  3. 客户端请求资源,资源分为静态资源(html)和动态资源(servlet)
  4. 资源返回给客户端浏览器

*Tomcat的入口就是一个main函数

二,开发——准备工作

2.1 新建Maven工程

2.2 定义编译级别

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.dxh</groupId>
  7. <artifactId>MiniCat</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <build>
  10. <plugins>
  11. <plugin>
  12. <groupId>org.apache.maven.plugins</groupId>
  13. <artifactId>maven-compiler-plugin</artifactId>
  14. <version>3.1</version>
  15. <configuration>
  16. <source>11</source>
  17. <target>11</target>
  18. <encoding>utf-8</encoding>
  19. </configuration>
  20. </plugin>
  21. </plugins>
  22. </build>
  23. </project>

2.3 新建主类编写启动入口和端口

这里我们把socket监听的端口号定义在主类中。

  1. package server;
  2. /**
  3. * Minicat的主类
  4. */
  5. public class Bootstrap {
  6. /**
  7. * 定义Socket监听的端口号
  8. */
  9. private int port = 8080;
  10. public int getPort() {
  11. return port;
  12. }
  13. public void setPort(int port) {
  14. this.port = port;
  15. }
  16. /**
  17. * Minicat的启动入口
  18. * @param args
  19. */
  20. public static void main(String[] args) {
  21. }
  22. }

三,开发——1.0版本

循序渐进,一点一点的完善,1.0版本我们需要的需求是:

  • 浏览器请求http://localhost:8080,返回一个固定的字符串到页面“Hello Minicat”

3.1 编写start方法以及遇到的问题

start方法主要就是监听上面配置的端口,然后得到其输出流,最后写出。

  1. /**
  2. * MiniCat启动需要初始化展开的一些操作
  3. */
  4. public void start() throws IOException {
  5. /*
  6. 完成Minicat 1.0版本
  7. 需求:浏览器请求http://localhost:8080,返回一个固定的字符串到页面“Hello Minicat!”
  8. */
  9. ServerSocket serverSocket = new ServerSocket(port);
  10. System.out.println("========>>Minicat start on port:"+port);
  11. while(true){
  12. Socket socket = serverSocket.accept();
  13. //有了socket,接收到请求,获取输出流
  14. OutputStream outputStream = socket.getOutputStream();
  15. outputStream.write("Hello Minicat!".getBytes());
  16. socket.close();
  17. }
  18. }

完整的代码:


  1. /**
  2. * Minicat的主类
  3. */
  4. public class Bootstrap {
  5. /**
  6. * 定义Socket监听的端口号
  7. */
  8. private int port = 8080;
  9. public int getPort() {
  10. return port;
  11. }
  12. public void setPort(int port) {
  13. this.port = port;
  14. }
  15. /**
  16. * Minicat的启动入口
  17. * @param args
  18. */
  19. public static void main(String[] args) {
  20. Bootstrap bootstrap = new Bootstrap();
  21. try {
  22. //启动Minicat
  23. bootstrap.start();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. /**
  29. * MiniCat启动需要初始化展开的一些操作
  30. */
  31. public void start() throws IOException {
  32. ServerSocket serverSocket = new ServerSocket(port);
  33. System.out.println("========>>Minicat start on port:"+port);
  34. while(true){
  35. Socket socket = serverSocket.accept();
  36. //有了socket,接收到请求,获取输出流
  37. OutputStream outputStream = socket.getOutputStream();
  38. outputStream.write("Hello Minicat!".getBytes());
  39. socket.close();
  40. }
  41. }
  42. }

此时,如果启动项目,从浏览器中输入http://localhost:8080/,能够正常接收到请求吗?

不能!

问题分析:

启动项目,从浏览器中输入http://localhost:8080/,可看到返回结果如下图:

因为Http协议是一个应用层协议,其规定了请求头、请求体、响应同样,如果没有这些东西的话浏览器无法正常显示。代码中直接把”Hello Minicat!“直接输出了,

3.2 解决问题,修改代码:

  1. 新建一个工具类,主要提供响应头信息

    1. package server;
    2. /**
    3. * http协议工具类,主要提供响应头信息,这里我们只提供200和404的情况
    4. */
    5. public class HttpProtocolUtil {
    6. /**
    7. * 为响应码200提供请求头信息
    8. */
    9. public static String getHttpHeader200(long contentLength){
    10. return "HTTP/1.1 200 OK \n" +
    11. "Content-Type: text/html \n" +
    12. "Content-Length: "+contentLength +"\n"+
    13. "\r\n";
    14. }
    15. /**
    16. * 为响应码404提供请求头信息(也包含了数据内容)
    17. */
    18. public static String getHttpHeader404(){
    19. String str404="<h1>404 not found</h1>";
    20. return "HTTP/1.1 404 NOT Found \n" +
    21. "Content-Type: text/html \n" +
    22. "Content-Length: "+str404.getBytes().length +"\n"+
    23. "\r\n" + str404;
    24. }
    25. }
  2. 修改start方法

    1. public void start() throws IOException {
    2. ServerSocket serverSocket = new ServerSocket(port);
    3. System.out.println("========>>Minicat start on port:"+port);
    4. while(true){
    5. Socket socket = serverSocket.accept();
    6. OutputStream outputStream = socket.getOutputStream();
    7. String data = "Hello Minicat!";
    8. String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
    9. outputStream.write(responseText.getBytes());
    10. socket.close();
    11. }
    12. }
  3. 访问~

成功。

四,开发——2.0版本

需求:

  • 封装Request和Response对象
  • 返回html静态资源文件

4.1 封装前准备

新建一个类,Bootstrap2 (为了方便与1.0版本做对比)。获得输入流,并打印出来看看。

  1. package server;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. /**
  8. * Minicat的主类
  9. */
  10. public class Bootstrap2 {
  11. /**
  12. * 定义Socket监听的端口号
  13. */
  14. private int port = 8080;
  15. public int getPort() {
  16. return port;
  17. }
  18. public void setPort(int port) {
  19. this.port = port;
  20. }
  21. /**
  22. * Minicat的启动入口
  23. * @param args
  24. */
  25. public static void main(String[] args) {
  26. Bootstrap2 bootstrap = new Bootstrap2();
  27. try {
  28. //启动Minicat
  29. bootstrap.start();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. /**
  35. * MiniCat启动需要初始化展开的一些操作
  36. */
  37. public void start() throws IOException {
  38. ServerSocket serverSocket = new ServerSocket(port);
  39. System.out.println("========>>Minicat start on port:"+port);
  40. while (true){
  41. Socket socket = serverSocket.accept();
  42. InputStream inputStream = socket.getInputStream();
  43. //从输入流中获取请求信息
  44. int count = 0 ;
  45. while (count==0){
  46. count = inputStream.available();
  47. }
  48. byte[] bytes = new byte[count];
  49. inputStream.read(bytes);
  50. System.out.println("请求信息=====>>"+new String(bytes));
  51. socket.close();
  52. }
  53. }
  54. }

打印出来的信息:

这里我们需要得到的是 请求方式(GET) 和 url (/) ,接下来封装Request的时候也是只封装这两个属性

4.2封装Request、Response对象

4.2.1 封装Request

只封装两个参数——method和url

  1. 新建Request类

  2. 该类有三个属性(String methodString urlInputStream inputStream

    method和url都是从input流中解析出来的。

  3. GET SET方法

  4. 编写有参构造

    1. /**
    2. * 构造器 输入流传入
    3. */
    4. public Request(InputStream inputStream) throws IOException {
    5. this.inputStream = inputStream;
    6. //从输入流中获取请求信息
    7. int count = 0 ;
    8. while (count==0){
    9. count = inputStream.available();
    10. }
    11. byte[] bytes = new byte[count];
    12. inputStream.read(bytes);
    13. String inputsStr = new String(bytes);
    14. //获取第一行数据
    15. String firstLineStr = inputsStr.split("\\n")[0]; //GET / HTTP/1.1
    16. String[] strings = firstLineStr.split(" ");
    17. //把解析出来的数据赋值
    18. this.method=strings[0];
    19. this.url= strings[1];
    20. System.out.println("method=====>>"+method);
    21. System.out.println("url=====>>"+url);
    22. }
  5. 无参构造

完整的Request.java

  1. package server;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. /**
  5. * 把我们用到的请求信息,封装成Response对象 (根据inputSteam输入流封装)
  6. */
  7. public class Request {
  8. /**
  9. * 请求方式 例如:GET/POST
  10. */
  11. private String method;
  12. /**
  13. * / , /index.html
  14. */
  15. private String url;
  16. /**
  17. * 其他的属性都是通过inputStream解析出来的。
  18. */
  19. private InputStream inputStream;
  20. /**
  21. * 构造器 输入流传入
  22. */
  23. public Request(InputStream inputStream) throws IOException {
  24. this.inputStream = inputStream;
  25. //从输入流中获取请求信息
  26. int count = 0 ;
  27. while (count==0){
  28. count = inputStream.available();
  29. }
  30. byte[] bytes = new byte[count];
  31. inputStream.read(bytes);
  32. String inputsStr = new String(bytes);
  33. //获取第一行数据
  34. String firstLineStr = inputsStr.split("\\n")[0]; //GET / HTTP/1.1
  35. String[] strings = firstLineStr.split(" ");
  36. this.method=strings[0];
  37. this.url= strings[1];
  38. System.out.println("method=====>>"+method);
  39. System.out.println("url=====>>"+url);
  40. }
  41. public Request() {
  42. }
  43. public String getMethod() {
  44. return method;
  45. }
  46. public void setMethod(String method) {
  47. this.method = method;
  48. }
  49. public String getUrl() {
  50. return url;
  51. }
  52. public void setUrl(String url) {
  53. this.url = url;
  54. }
  55. a
  56. public InputStream getInputStream() {
  57. return inputStream;
  58. }
  59. public void setInputStream(InputStream inputStream) {
  60. this.inputStream = inputStream;
  61. }
  62. }

4.2.2 封装Response

  1. package server;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.io.OutputStream;
  6. /**
  7. * 封装Response对象,需要依赖于OutputStream
  8. *
  9. */
  10. public class Response{
  11. private OutputStream outputStream;
  12. public Response(OutputStream outputStream) {
  13. this.outputStream = outputStream;
  14. }
  15. public Response() {
  16. }
  17. /**
  18. * @param path 指的就是 Request中的url ,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过输出流输出
  19. */
  20. public void outputHtml(String path) throws IOException {
  21. //获取静态资源的绝对路径
  22. String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
  23. //输出静态资源文件
  24. File file = new File(absoluteResourcePath);
  25. if (file.exists() && file.isFile()){
  26. //读取静态资源文件,输出静态资源
  27. StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
  28. }else{
  29. //输出404
  30. output(HttpProtocolUtil.getHttpHeader404());
  31. }
  32. }
  33. //使用输出流输出指定字符串
  34. public void output(String context) throws IOException {
  35. outputStream.write(context.getBytes());
  36. }
  37. }

2.0版本只考虑输出静态资源文件

我们来分析一下outputHtml(String path)这个方法

首先,path就指 Request中的url,我们要用这个url找到该资源的绝对路径:

  1. 根据path,获取静态资源的绝对路径

    1. public static String getAbsolutePath(String path){
    2. String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
    3. return absolutePath.replaceAll("\\\\","/")+path;
    4. }
  2. 判断静态资源是否存在

    • 不存在:输出404
  3. 存在:读取静态资源文件,输出静态资源

    1. public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
    2. int count = 0 ;
    3. while (count==0){
    4. count=inputStream.available();
    5. }
    6. //静态资源长度
    7. int resourceSize = count;
    8. //输出Http请求头 , 然后再输出具体的内容
    9. outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
    10. //读取内容输出
    11. long written = 0; //已经读取的内容长度
    12. int byteSize = 1024; //计划每次缓冲的长度
    13. byte[] bytes = new byte[byteSize];
    14. while (written<resourceSize){
    15. if (written+byteSize >resourceSize){ //剩余未读取大小不足一个1024长度,那就按照真实长度处理
    16. byteSize= (int)(resourceSize-written); //剩余的文件内容长度
    17. bytes=new byte[byteSize];
    18. }
    19. inputStream.read(bytes);
    20. outputStream.write(bytes);
    21. outputStream.flush();
    22. written+=byteSize;
    23. }
    24. }

把上述的第一步和第三步的方法封装到一个类中:

  1. package server;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. public class StaticResourceUtil {
  6. /**
  7. * 获取静态资源方法的绝对路径
  8. */
  9. public static String getAbsolutePath(String path){
  10. String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
  11. return absolutePath.replaceAll("\\\\","/")+path;
  12. }
  13. /**
  14. * 读取静态资源文件输入流,通过输出流输出
  15. */
  16. public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
  17. int count = 0 ;
  18. while (count==0){
  19. count=inputStream.available();
  20. }
  21. //静态资源长度
  22. int resourceSize = count;
  23. //输出Http请求头 , 然后再输出具体的内容
  24. outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
  25. //读取内容输出
  26. long written = 0; //已经读取的内容长度
  27. int byteSize = 1024; //计划每次缓冲的长度
  28. byte[] bytes = new byte[byteSize];
  29. while (written<resourceSize){
  30. if (written+byteSize >resourceSize){ //剩余未读取大小不足一个1024长度,那就按照真实长度处理
  31. byteSize= (int)(resourceSize-written); //剩余的文件内容长度
  32. bytes=new byte[byteSize];
  33. }
  34. inputStream.read(bytes);
  35. outputStream.write(bytes);
  36. outputStream.flush();
  37. written+=byteSize;
  38. }
  39. }
  40. }

测试:

  1. 修改Bootstrap2.java中的start()方法

    1. public void start() throws IOException {
    2. ServerSocket serverSocket = new ServerSocket(port);
    3. System.out.println("========>>Minicat start on port:"+port);
    4. while (true){
    5. Socket socket = serverSocket.accept();
    6. InputStream inputStream = socket.getInputStream();
    7. //封装Resuest对象和Response对象
    8. Request request = new Request(inputStream);
    9. Response response = new Response(socket.getOutputStream());
    10. response.outputHtml(request.getUrl());
    11. socket.close();
    12. }
    13. }
  2. 在项目的resources文件夹新建index.html文件

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Static resource </title>
    6. </head>
    7. <body>
    8. Hello ~ Static resource
    9. </body>
    10. </html>
  3. 运行main方法

  4. 浏览器输入:http://localhost:8080/index.html

  5. 结果展现:

五,开发——3.0版本

3.0版本就要定义Servlet了,大致分为以下几步:

  1. 定义servlet规范
  2. 编写Servlet
  3. 加载解析Servlet配置

5.1 定义servlet规范

  1. public interface Servlet {
  2. void init() throws Exception;
  3. void destroy() throws Exception;
  4. void service(Request request,Response response) throws Exception;
  5. }

定义一个抽象类,实现Servlet,并且增加两个抽象方法doGet , doPost.

  1. public abstract class HttpServlet implements Servlet{
  2. public abstract void doGet(Request request,Response response);
  3. public abstract void doPost(Request request,Response response);
  4. @Override
  5. public void init() throws Exception {
  6. }
  7. @Override
  8. public void destroy() throws Exception {
  9. }
  10. @Override
  11. public void service(Request request, Response response) throws Exception {
  12. if ("GET".equals(request.getMethod())){
  13. doGet(request, response);
  14. }else{
  15. doPost(request, response);
  16. }
  17. }
  18. }

5.2 编写Servlet继承HttpServlet

新建DxhServlet.java,并继承HttpServlet重写doGet和doPost方法

  1. package server;
  2. import java.io.IOException;
  3. public class DxhServlet extends HttpServlet{
  4. @Override
  5. public void doGet(Request request, Response response) {
  6. String content="<h1>DxhServlet get</h1>";
  7. try {
  8. response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. @Override
  14. public void doPost(Request request, Response response) {
  15. String content="<h1>DxhServlet post</h1>";
  16. try {
  17. response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. @Override
  23. public void init() throws Exception {
  24. super.init();
  25. }
  26. @Override
  27. public void destroy() throws Exception {
  28. super.destroy();
  29. }
  30. }

接下来要把DxhServlet配置到一个配置文件中,当MiniCat启动时,加载进去。

5.3 加载解析Servlet配置

5.3.1 配置文件

resources目录下,新建web.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <web-app>
  3. <servlet>
  4. <servlet-name>dxh</servlet-name>
  5. <servlet-class>server.DxhServlet</servlet-class>
  6. </servlet>
  7. <servlet-mapping>
  8. <servlet-name>dxh</servlet-name>
  9. <url-pattern>/dxh</url-pattern>
  10. </servlet-mapping>
  11. </web-app>

标准的配置Servlet的标签。servlet-class改成自己写的Servlet全限定类名,url-pattern/dxh,一会请求http://localhost:8080/dxh,来访问这个servlet

5.3.2 解析配置文件

复制一份Bootstrap2.java,命名为Bootstrap3.java

  1. 加载解析相关的配置 ,web.xml

    引入dom4j和jaxen的jar包

    1. <dependency>
    2. <groupId>dom4j</groupId>
    3. <artifactId>dom4j</artifactId>
    4. <version>1.6.1</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>jaxen</groupId>
    8. <artifactId>jaxen</artifactId>
    9. <version>1.1.6</version>
    10. </dependency>
  2. Bootstrap3.java中增加一个方法

    1. //用于下面存储url-pattern以及其对应的servlet-class的实例化对象
    2. private Map<String,HttpServlet> servletMap = new HashMap<>();
    3. private void loadServlet(){
    4. InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
    5. SAXReader saxReader = new SAXReader();
    6. try {
    7. Document document = saxReader.read(resourceAsStream);
    8. //根元素
    9. Element rootElement = document.getRootElement();
    10. /**
    11. * 1, 找到所有的servlet标签,找到servlet-name和servlet-class
    12. * 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
    13. */
    14. List<Element> selectNodes = rootElement.selectNodes("//servlet");
    15. for (int i = 0; i < selectNodes.size(); i++) {
    16. Element element = selectNodes.get(i);
    17. /**
    18. * 1, 找到所有的servlet标签,找到servlet-name和servlet-class
    19. */
    20. //<servlet-name>dxh</servlet-name>
    21. Element servletNameElement =(Element)element.selectSingleNode("servlet-name");
    22. String servletName = servletNameElement.getStringValue();
    23. //<servlet-class>server.DxhServlet</servlet-class>
    24. Element servletClassElement =(Element)element.selectSingleNode("servlet-class");
    25. String servletClass = servletClassElement.getStringValue();
    26. /**
    27. * 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>
    28. */
    29. //Xpath表达式:从/web-app/servlet-mapping下查询,查询出servlet-name=servletName的元素
    30. Element servletMapping =(Element)rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']'");
    31. // /dxh
    32. String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
    33. servletMap.put(urlPattern,(HttpServlet) Class.forName(servletClass).newInstance());
    34. }
    35. } catch (DocumentException e) {
    36. e.printStackTrace();
    37. } catch (IllegalAccessException e) {
    38. e.printStackTrace();
    39. } catch (InstantiationException e) {
    40. e.printStackTrace();
    41. } catch (ClassNotFoundException e) {
    42. e.printStackTrace();
    43. }
    44. }

    这段代码的意思就是读取web.xml转换成Document,然后遍历根元素内中的servlet标签(servlet是可以配置多个的),通过XPath表达式获得servlet-nameservlet-class,以及与其对应的<servlet-mapping>标签下的url-pattern,然后存在Map中。注意,这里Map的Key是url-patternValue是servlet-class的实例化对象

5.4 接收请求,处理请求改造

这里进行了判断,判断servletMap中是否存在url所对应的value,如果没有,当作静态资源访问,如果有,取出并调用service方法,在HttpServlet的service方法中已经做了根据request判断具体调用的是doGet还是doPost方法。

测试:

在浏览器中输入:

http://localhost:8080/index.html,可以访问静态资源

输入:http://localhost:8080/dxh

可以访问【5.2中编写的Servlet】动态资源~

到此位置,一个简单的Tomcat Demo已经完成。

六,优化——多线程改造(不使用线程池)

6.1 问题分析

在现有的代码中,接收请求这部分它是一个IO模型——BIO,阻塞IO。

它存在一个问题,当一个请求还未处理完成时,再次访问,会出现阻塞的情况。

可以在DxhServletdoGet方法中加入Thread.sleep(10000);然后访问http://localhost:8080/dxhhttp://localhost:8080/index.html做个测试

那么我们可以使用多线程对其进行改造。

把上述代码放到一个新的线程中处理。

6.2 复制Bootstrap3

复制Bootstrap3,命名为Bootstrap4。把start()方法中上图的部分(包括socket.close()剪切到下面的线程处理类的run方法中:

6.3 定义一个线程处理类

  1. package server;
  2. import java.io.InputStream;
  3. import java.net.Socket;
  4. import java.util.Map;
  5. /**
  6. * 线程处理类
  7. */
  8. public class RequestProcessor extends Thread{
  9. private Socket socket;
  10. private Map<String,HttpServlet> servletMap;
  11. public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
  12. this.socket = socket;
  13. this.servletMap = servletMap;
  14. }
  15. @Override
  16. public void run() {
  17. try{
  18. InputStream inputStream = socket.getInputStream();
  19. //封装Resuest对象和Response对象
  20. Request request = new Request(inputStream);
  21. Response response = new Response(socket.getOutputStream());
  22. String url = request.getUrl();
  23. //静态资源处理
  24. if (servletMap.get(url)==null){
  25. response.outputHtml(request.getUrl());
  26. }else{
  27. //动态资源处理
  28. HttpServlet httpServlet = servletMap.get(url);
  29. httpServlet.service(request,response);
  30. }
  31. socket.close();
  32. }catch (Exception e){
  33. }
  34. }
  35. }

6.4 修改Bootstrap4的start()方法

  1. public void start() throws Exception {
  2. //加载解析相关的配置 ,web.xml,把配置的servlet存入servletMap中
  3. loadServlet();
  4. ServerSocket serverSocket = new ServerSocket(port);
  5. System.out.println("========>>Minicat start on port:"+port);
  6. /**
  7. * 可以请求动态资源
  8. */
  9. while (true){
  10. Socket socket = serverSocket.accept();
  11. //使用多线程处理
  12. RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
  13. requestProcessor.start();
  14. }
  15. }

再次做6.1章节的测试, OK 没有问题了。

七,优化——多线程改造(使用线程池)

这一步,我们使用线程池进行改造。

复制Bootstrap4,命名为Bootstrap5。

修改start()方法。线程池的使用不再赘述。代码如下:

  1. public void start() throws Exception {
  2. //加载解析相关的配置 ,web.xml,把配置的servlet存入servletMap中
  3. loadServlet();
  4. /**
  5. * 定义线程池
  6. */
  7. //基本大小
  8. int corePoolSize = 10;
  9. //最大
  10. int maxPoolSize = 50;
  11. //如果线程空闲的话,超过多久进行销毁
  12. long keepAliveTime = 100L;
  13. //上面keepAliveTime的单位
  14. TimeUnit unit = TimeUnit.SECONDS;
  15. //请求队列
  16. BlockingQueue<Runnable> workerQueue = new ArrayBlockingQueue<>(50);
  17. //线程工厂,使用默认的即可
  18. ThreadFactory threadFactory = Executors.defaultThreadFactory();
  19. //拒绝策略,如果任务太多处理不过来了,如何拒绝
  20. RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
  21. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize
  22. ,maxPoolSize
  23. ,keepAliveTime
  24. ,unit
  25. ,workerQueue
  26. ,threadFactory
  27. ,handler);
  28. ServerSocket serverSocket = new ServerSocket(port);
  29. System.out.println("========>>Minicat start on port(多线程):"+port);
  30. /**
  31. * 可以请求动态资源
  32. */
  33. while (true){
  34. Socket socket = serverSocket.accept();
  35. RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
  36. threadPoolExecutor.execute(requestProcessor);
  37. }
  38. }

OK ,再次测试,成功~

MINI版Tomcat到此完成。

八,总结

总结一下编写一个MINI版本的Tomcat都需要做些什么:

  1. 定义一个入口类,需要监听的端口号和入口方法——main方法
  2. 定义servlet规范(接口),并实现它——HttpServlet
  3. 编写http协议工具类,主要提供响应头信息
  4. 在main方法中调用start()方法用于启动初始化和请求进来时的操作
  5. 加载解析配置文件(web.xml)
  6. 当请求进来时,解析inputStream,并封装为Request和Response对象。
  7. 判断请求资源的方式(动态资源还是静态资源)

【Tomcat】手写迷你版Tomcat的更多相关文章

  1. Netty核心组件介绍及手写简易版Tomcat

    Netty是什么: 异步事件驱动框架,用于快速开发高i性能服务端和客户端 封装了JDK底层BIO和NIO模型,提供高度可用的API 自带编码解码器解决拆包粘包问题,用户只用关心业务逻辑 精心设计的Re ...

  2. 手写迷你Tomcat

    手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat手写迷你Tomcat ...

  3. 我手写的简易tomcat

    前述 自己手写的简易的tomcat,实现了tomcat的基本响应功能,项目代码已经上传到我的Github,刚刚开始学习这里,当前还存在很多问题 项目简述及代码 当我们的Web运行的时候,从浏览器发出的 ...

  4. 手写一个简化版Tomcat

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

  5. 手写迷你SpringMVC框架

    前言 学习如何使用Spring,SpringMVC是很快的,但是在往后使用的过程中难免会想探究一下框架背后的原理是什么,本文将通过讲解如何手写一个简单版的springMVC框架,直接从代码上看框架中请 ...

  6. 手写mini版MVC框架

    目录 1, Springmvc基本原理流程 2,注解开发 编写测试代码: 目录结构: 3,编写自定义DispatcherServlet中的初始化流程: 3.1 加载配置文件 3.2 扫描相关的类,扫描 ...

  7. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

  8. mybatis(八)手写简易版mybatis

    一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...

  9. 手写简易版Promise

    实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了. ...

随机推荐

  1. C/C++面试题:C++与C有什么不同?

    昨天,小编在一个讨论群里看到这样的对话     有人想要入学校编程俱乐部,面试时,学长问了她C++的区别,她没有答上来,就没有通过. 说到C和C++的区别,不只是进入学校社团有考核,出了学校,找工作面 ...

  2. libev使用方法

    1. libev简介 libev是个高性能跨平台的事件驱动框架,支持io事件,超时事件,子进程状态改变通知,信号通知,文件状态改变通知,还能用来实现wait/notify机制.libev对每种监听事件 ...

  3. java多态2

    1 package pet_2; 2 3 public class Pet { 4 private String name; 5 6 public String getName() { 7 retur ...

  4. 手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师.官方网站:devui.designNg组件库:ng-devui(欢迎S ...

  5. Django----View.py

    ·首先先下载安装包· pip install djangorestframework==3.11.1 pip install django-filter==2.3.0 # 过滤器 pip instal ...

  6. IDEA社区版(Community)和付费版(UItimate)的区别

    比对类型 Ultimate(终极版,付费) Community(社区版,免费) 语言支持 Java Java Groovy Groovy Kotlin Kotlin Scala(通过插件) Scala ...

  7. NOIp2020游记

    Day 1 考点还是在南航,第三次去已经没有什么新鲜感了,满脑子都是NOIp能不能考好.考前奶了一波这次必考最短路,于是在试机的时候打了一遍Dij和SPFA的板子,信心满满的上场了. 考试右后方是Ki ...

  8. 最常用的分布式ID解决方案,你知道几个

    一.分布式ID概念 说起ID,特性就是唯一,在人的世界里,ID就是身份证,是每个人的唯一的身份标识.在复杂的分布式系统中,往往也需要对大量的数据和消息进行唯一标识.举个例子,数据库的ID字段在单体的情 ...

  9. PyQt(Python+Qt)学习随笔:Qt Designer中部件的调色板palette属性和字体font属性设置

    一.调色板 在Qt Designer的部件属性中,有个部件调色板(palette)的属性,进入后,如下图所示: 1.调色板palette Qt中提供的调色板palette用于管理控件的外观显示,对应P ...

  10. [BJDCTF 2nd]old-hack && [GXYCTF2019]禁止套娃

    [BJDCTF 2nd]old-hack 页面很有意思 同时也告诉了我们是THINKPHP5,我们只需要寻找THINKPHP5的漏洞就可以了. https://www.codercto.com/a/5 ...