【JAVA基础】实现Tomcat基本功能
文章目录
- TCP/IP协议
- Socket编程
- Servlet
- Tomcat
在搜索了两三天之后,也是大概弄懂了Tomcat是个什么东西,我们在说Tomcat之前,先来了解一下下面这三个东西:
TCP/IP协议
TCP/IP 是互联网通信的基础协议。TCP(传输控制协议)负责可靠的数据传输,IP(互联网协议)负责数据包的路由和地址定位。所有的网络通信,包括服务器和客户端之间的通信,都依赖于 TCP/IP 协议。包括五层结构,自上而下分别为:应用层、传输层、网络层、数据链路层、物理层
- 应用层:发送端想要发送数据,需要应用层准备好要发送的数据,直接与用户进行对接,之后交给传输层
- 传输层:传输层的主要作用是为发送端和接收端提供可靠的服务,举个例子,就像我们寄送快递时候的物流公司,确保我们的商品不受损坏
- 网络层:网络层负责选择数据传输的路线,应该走哪个路由器等等
- 数据链路层:选择好传输路径之后,由数据链路层将数据从一个路由器发送到另外一个路由器
- 物理层:可以理解为网线等等数据需要走的路线
Socket编程
百度百科上面对Socket的解释是:**套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。**我是这么理解的:Socket编程其实是应用程序进行数据传输的一种方式,它是TCP/IP的一种具体实现,计算机通过Socket协议区分数据应该输送到哪个具体的应用程序,Socket用于监听TCP/IP连接(例如:浏览器中的HTTP请求)
Servlet
Servlet
包含于Tomcat中,实质上是一个Java类,是一种运行在支持Java应用服务器上的Web组件,它的作用是响应Socket监听到的HTTP请求,并做出相应处理。其中会包含一个HttpServlet
类,是Servlet API
的核心类,其自定义的Servlet都是该类的子类。
Tomcat
介绍完上面内容后,我们来看什么是Tomcat:Tomcat是一个基于 Socket 通信的 Java 应用服务器,专门用来运行 Java Servlet,也就是说Tomcat其实是Servlet的容器,它基于Socket通信来获取浏览器中的请求,并做出相应处理,将处理结果交给Servlet,让其做出响应,并将结果再反馈给客户端浏览器。下面是Tomcat运行的基本流程:
下面写一下代码实现:
我们先定义Servlet类,定义其doGet()
和doPost()
方法以便于对浏览器请求做出响应,service()
方法用于判断浏览器的请求类型,并调用对应的方法
// HttpServlet接口 定义了Servlet中的核心方法
@WebServlet(url = "/Http")
public interface HttpServlet {
// 接口中定义servlet类中的核心方法
void service(Request request);
void doGet(Request request) ;
void doPost(Request request) ;
}
// servlet类 继承接口并对接口中的方法进行实现
@WebServlet(url = "/first")
public class MyFirstServlet implements HttpServlet{
// 对传入的请求方法进行判断,属于那种请求,调用响应的doGet或者doPost方法
public void service(Request request){
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request);
}
}
public void doGet(Request request){
System.out.println("hello get1");
}
public void doPost(Request request){
System.out.println("hello post1");
}
}
@WebServlet(url = "/second")
public class MySecondServlet implements HttpServlet{
// 对传入的请求方法进行判断,属于那种请求,调用响应的doGet或者doPost方法
public void service(Request request){
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request);
}
}
public void doGet(Request request){
System.out.println("hello get2");
}
public void doPost(Request request){
System.out.println("hello post2");
}
}
定义完Servlet
类后,我们来看Tomcat服务器中的核心功能,我们将它分为五步完成:
-
扫描Servlet包下面的类,并获取到所有的类的全类名
要想让Tomcat将获取到的请求数据传输给
Servlet
类,那就必须先获取到Servlet
类的类信息,那么我们对目录进行扫描,并定位到Servlet
类所在的文件目录:// 定义一个集合用于存储访问地址以及类对象 static Map<String, Class<?>> servletMaps = new HashMap<>(); // 定义一个集合用于存储类名 static List<String> classPaths = new ArrayList<>(); public static void main(String[] args) throws IOException { // 查找servlet类名并存储 searchClass(); } // 用于拼接Servlet包所在根目录,并递归进行查找 private static List<String> searchClass(){ String basePack = "servlet"; // 包目录 String classPath = MyTomcat.class.getResource("/").getPath(); // 获取文件所在的根目录 basePack = basePack.replace(".", File.separator); // 将包名中的 . 替换为路径中的 \ // 拼接完整地址 String searchPath = classPath + basePack; // 递归通过路径获取类名 doPath(new File(searchPath), classPath); // 将最后的集合值返回给main()方法 return classPaths; } // 递归获取Servlet文件名 private static void doPath(File file, String classpath) { // 递归条件 if (file.isDirectory()) { // 判断是否是文件夹,如果是就继续递归 File[] files = file.listFiles(); for (File f1 : files) { doPath(f1, classpath); } } else { // 如果是文件,就对名字进行处理,然后存储到classPaths集合中 if (file.getName().endsWith(".class")) { String path = file.getPath().replace(classpath.replace("/", "\\") .replaceFirst("\\\\", ""), "") .replace("\\", ".") .replace(".class", ""); classPaths.add(path); } } }
上面的代码中,
searchClass()
方法用于初始化Servlet包所在的根目录,并调用doPath()
方法,对目录进行进一步查找,直到找到我们需要的Servlet类文件 -
根据第一步获取到的全类名生成类对象
创建想对应的类对象之后,我们将类对象以及他的类名,用Key-Value键值对的形式存储到servletMap集合中,方便之后的使用
-
获取类上面注解的访问地址
我们知道浏览器发送的请求是需要
Servlet
处理的,但是每一个请求都需要调用到对应的Servlet
类才可以,所以我们获取到Servlet
类上面的注解,用于之后的对比。下面是对应的二三步代码:
// 对存储着Servlet类名的集合进行扫描,创建servlet对象 for (String className : classPaths) { try { // 创建对应的servlet类对象 Class<?> clazz = Class.forName(className); // 获取servlet类上面的注解内容 WebServlet webServlet = clazz.getDeclaredAnnotation(WebServlet.class); // 存储在集合中 servletMaps.put(webServlet.url(), clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } }
-
如果请求内容和
@WebServlet
当中的注解是相同的,那么生成Servlet
对象这一步在实现的时候其实有点曲折,第一次我直接在
Tomcat
类中想要创建Servlet
对象,但是发现Request
中存储的请求内容我是没法直接获取到的;于是第二次我又直接在Server
类中去创建Servlet
对象,但是这里没法获取到Servlet
类对象信息;直到这个时候我才反应过来应该在Tomcat
中创建阻塞监听,这样才能获取到请求方法,并创建对应的Servlet
对象。// 定义一个Request对象用于存储请求信息 static Request request = new Request(); // Request类 用于存储请求方法和请求内容 public class Request { String Method; String Url; public void setMethod(String setMethod) { this.Method = setMethod; } public void setUrl(String setUrl) { this.Url = setUrl; } public String getMethod() { return Method; } public String getUrl() { return Url; } } public static void main(String[] args) throws IOException { // 启动tomcat服务器 start(4700); } // 启动tomcat服务器,其中对端口进行监听,并获取到请求信息 public static void start(int port) throws IOException { ServerSocket serverSocket = new ServerSocket(port); System.out.println("MyTomcat 启动,监听端口:" + port); while(true) { Socket socket = serverSocket.accept(); // 阻塞监听 // 打开输入流,解析客户端发来的内容 InputStream inputStream = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); //将 01010这样的bit信息 转换为 字符数据 String line = reader.readLine(); // 读取第一行,它包含了请求方法和请求路径等信息 if (line != null && !line.isEmpty()){ String requestContent = line.split(" ")[1]; // String requestMethod = line.split(" ")[0]; request.setUrl(requestContent); request.setMethod(requestMethod); doRequest(request); } } } // 对Request类中存储的请求内容和方法进行处理和比对 public static void doRequest(Request request) { // 存储获取到的请求内容 String requestUrl = request.getUrl(); for (String servletKey : servletMaps.keySet()){ // 判断获取到的请求信息是否和servlet类对象上面的注解名称一样,如果一样那么创建对应类信息 if (servletKey.equals(requestUrl)){ Class<?> servletClass = servletMaps.get(requestUrl); // System.out.println(servletClass); 做一个输出用于测试是否获取 // 如果创建了对应的类对象,那么就开始创建对应的servlet对象,并调用其中的方法 if (servletClass != null) { servletClass.newInstance(); // 创建servlet对象 } else { System.out.println("404 Not Found: " + requestUrl); } } } }
上面的
start()
方法用于启动tomcat服务器,并监听端口,随时获取浏览器发送的请求,并对请求内容进行处理,将请求方法,和请求内容分别储存在Request
类中的对象中,Request
类用于存储浏览器中发来的请求,doRequest()
方法对获取到的请求方法和请求内容进行处理和比对,并创建Servlet
对象 -
根据浏览器请求调用Servlet类中的
doPost()
和doGet()
方法这一步我是搜了好多资料加上老师的提示才写出来的,最后这两步对于我这个初学者实在思考起来有点费劲,需要用到一些代理的知识,因为我们可以发现,在创建完
Servlet
对象之后,我们没办法对这个对象进行存储,那么我们也就没法通过请求方法进行判断,调用servlet中对应的方法进行响应,我们只能是手动去调用方法。
那么我们可以使用代理,通过我们创建的HttpServlet
接口来存储我们生成的Servlet
对象,然后通过httpServlet
对象调用service
方法,来进行判断,并对请求做出响应。在这里,Tomcat
相当于代理对象,Servlet
相当于核心对象,通过Tomcat
来代理调用到其中的doGet()
和doPost()
核心方法。为什么用代理?
我们可以发现创建的servlet类对象我们只能用Object类来接收,但是如果这样接收的话,我们也就无法调用到Servlet中的doGet等方法
所以我们让Servlet都继承同一个接口:HttpServlet
,这样我们就有办法来存储这个创建的Servlet对象,并在其中判断我们传入的请求方法,进行判断,做出对应的响应。此时Tomcat属于一个代理类核心代码其实没多少,但是逻辑我感觉第一次实现的时候不是很好想:
try { // 通过接口来存储生成的Servlet对象 HttpServlet httpServlet = (HttpServlet) servletClass.newInstance(); // 假设 Servlet 类中有一个 service() 方法来处理请求,其中对doGet和doPost方法进行调用 httpServlet.service(request); } catch (Exception e) { e.printStackTrace(); }