分享

从0手写实现Tomcat

 wxt208 2019-05-24

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

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

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

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

Socket实际上做了什么呢? 

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

底层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 却发现浏览器显示“无法访问该页面”。这是为什么呢?

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

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

Http协议 --- 请求

Http协议 --- 响应

为了实现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\r\n'.getBytes());          os.write('Content-Lenght: 11\r\n\r\n'.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,然后才能正常调用相应的方法。

项目类资源加载

通过以下代码,我们可以实现对项目类加载,获取到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服务器就算成功了。

代码地址:https://github.com/biaotang/demo-tomcat

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多