从0手写实现Tomcat

Posted 四阿哥胤禛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0手写实现Tomcat相关的知识,希望对你有一定的参考价值。

今天我们来一步一步实现从0开始手写Tomcat。


从0手写实现Tomcat

首先,我们要知道Tomcat是什么,能做什么。

Tomcat是一个Web网络应用程序,可以接收请求,并且可以处理请求。接收请求意味着必须要有一个端口,Tomcat默认http端口是8080,请求过来后由socket网络处理,请求最终需要交给线程处理,根据URL调用servlet,之后返回响应内容。

从0手写实现Tomcat

那么要实现Tomcat的功能,首先第一步我们要实现Socket编程。



从0手写实现Tomcat

Socket实际上做了什么呢? 

Socket调用操作系统的SocketAPI来实现网络处理。所谓网络编程,就是对外开放接口,让我们的程序可以与外部建立连接。

从0手写实现Tomcat

底层Socket API函数定义

  • listen()、accept()函数只能用于服务器端;

    • listen用于网络监听

    • accept用于获取连接

  • connect()函数只能用于客户端;

    • connect用于客户端连接服务端

  • socket()、bind()、send()、recv()、sendto()、recvfrom()、close()

    • socket用于获取套接字对象

    • bind用于绑定端口或其他信息

    • send、recv、sendto、recvfrom用于发送和接收数据

    • close用于关闭连接


了解完socket编程后,我们先来完成Socket网络处理部分,如下:

 private static ExecutorService threadPool = Executors.newCachedThreadPool();  public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("tomcat 服务器启动成功"); while (!serverSocket.isClosed()) { //获取Socket连接 Socket request = serverSocket.accept(); threadPool.execute(() -> { try (InputStream is = request.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)) ){ System.out.println("收到请求:"); String msg = null; while ((msg = reader.readLine()) != null) { if (msg.length() == 0) { break; } System.out.println(msg); } System.out.println("---------收到请求"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (request != null) { request.close(); } } catch (IOException e) { e.printStackTrace(); } } }); } if (serverSocket != null) { serverSocket.close(); }  }

  

完成Socket网络编程,我们启动服务后,通过telnet可以发现8080端口已启动,但是我们通过浏览器访问http://localhost:8080 却发现浏览器显示“无法访问该页面”。这是为什么呢?



从0手写实现Tomcat

我们的目标是要实现浏览器和Java Web服务器Tomcat实现交互,那么Java Web服务器Tomcat如何与浏览器交互呢?

从0手写实现Tomcat

Tomcat和浏览器之间要交互,需要约定一个协议,这样才能正常交互。

Http协议 --- 请求

从0手写实现Tomcat

Http协议 --- 响应

从0手写实现Tomcat



为了实现Java Web服务器Tomca与浏览器之间能正常交互,我们需要加上一段请求响应代码,完整代码如下:

 private static ExecutorService threadPool = Executors.newCachedThreadPool();  public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("tomcat 服务器启动成功"); while (!serverSocket.isClosed()) { //获取Socket连接 Socket request = serverSocket.accept(); threadPool.execute(() -> { try (InputStream is = request.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)) ){ System.out.println("收到请求:"); String msg = null; while ((msg = reader.readLine()) != null) { if (msg.length() == 0) { break; } System.out.println(msg); } System.out.println("---------收到请求"); //响应返回结果 200 OutputStream os = request.getOutputStream(); os.write("HTTP/1.1 200 OK
".getBytes()); os.write("Content-Lenght: 11

".getBytes()); os.write("Hello World".getBytes()); os.flush();  } catch (IOException e) { e.printStackTrace(); } finally { try { if (request != null) { request.close(); } } catch (IOException e) { e.printStackTrace(); } } }); } if (serverSocket != null) { serverSocket.close(); } }

这时候,我们启动服务后,再通过浏览器去访问http://localhost:8080/,我们会发现浏览器返回了“Hello World”。

接下来,我们要实现根据请求URL执行相应的servlet方法。

例如:“GET /servlet-demo-1.0.0/index HTTP/1.1”,我们要根据这样一个请求去找到对应的项目及调用相应的servlet。

首先我们要知道有哪几个项目,请求的项目是哪个,对应的servlet是哪个。通过请求路径,我们可以知道请求的项目名称是“servlet-demo-1.0.0”,请求的servlet路径是 /index 。我们可以根据项目名称查找到对应的项目,读取项目的web.xml,从web.xml中获取到Servlet对应的class,也可以获取到ServletMapping设置的路径来做请求过滤,也可以获取其他信息来做相应的一些操作。

这里有一个问题,获取到的class文件可以执行用来调用Servlet吗?

答案是肯定不行,了解JVM的应该都知道,我们需要先将class文件加载到JVM,然后才能正常调用相应的方法。



从0手写实现Tomcat



项目类资源加载

通过以下代码,我们可以实现对项目类加载,获取到Servlet实例。

//每个项目,类加载器,去加载置顶位置的class信息URL classUrl = new URL("file:" + projectPath + "\WEB-INF\classes\");URLClassLoader servletClassLoader = new URLClassLoader(new URL[] {classUrl});//1、 加载到JVMClass<?> servletClass = servletClassLoader.loadClass(servletClassName);//2、 实例化Servlet servlet = (Servlet) servletClass.newInstance();



我们先来回顾一下Servlet的生命周期是怎样的,如下所示:

在我们这个模拟例子中,我们就不实现init()和destory()了,我们就来调用service()方法处理客户端请求。javax.servlet.Servlet的service()方法是有两个参数的

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

我们在应用开发中常用的HttpServletRequest和HttpServletResponse,实际上是我们的Java Web服务器已经帮我们实现了,这里我们也需要来实现我们的HttpServletRequest和HttpServletResponse,然后在调用service()方法就可以了。到这里,我们简易版的Web服务器就算成功了。


以上是关于从0手写实现Tomcat的主要内容,如果未能解决你的问题,请参考以下文章

手写简化版SpringBoot

在Tomcat的安装目录下conf目录下的server.xml文件中增加一个xml代码片段,该代码片段中每个属性的含义与用途

手写一个简化版Tomcat

如何手写Tomcat框架

从零开始手写Tomcat的教程---未完待续

tomcat学习笔记手写tomcat