从0手写实现Tomcat
Posted 四阿哥胤禛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0手写实现Tomcat相关的知识,希望对你有一定的参考价值。
今天我们来一步一步实现从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 ".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,然后才能正常调用相应的方法。
项目类资源加载
通过以下代码,我们可以实现对项目类加载,获取到Servlet实例。
//每个项目,类加载器,去加载置顶位置的class信息
URL classUrl = new URL("file:" + projectPath + "\WEB-INF\classes\");
URLClassLoader servletClassLoader = new URLClassLoader(new URL[] {classUrl});
//1、 加载到JVM
Class<?> 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的主要内容,如果未能解决你的问题,请参考以下文章
在Tomcat的安装目录下conf目录下的server.xml文件中增加一个xml代码片段,该代码片段中每个属性的含义与用途