创作缘由
平时使用 tomcat 等 web 服务器不可谓不多,但是一直一知半解。
于是想着自己实现一个简单版本,学习一下 tomcat 的精髓。
系列教程
从零手写实现 apache Tomcat-01-入门先容
从零手写实现 apache Tomcat-02-web.xml 入门详细先容
从零手写实现 tomcat-03-根本的 socket 实现
从零手写实现 tomcat-04-哀求和响应的抽象
从零手写实现 tomcat-05-servlet 处理支持
从零手写实现 tomcat-06-servlet bio/thread/nio/netty 池化处理
从零手写实现 tomcat-07-war 怎样剖析处理三方的 war 包?
从零手写实现 tomcat-08-tomcat 怎样与 springboot 集成?
从零手写实现 tomcat-09-servlet 处理类
从零手写实现 tomcat-10-static resource 静态资源文件
从零手写实现 tomcat-11-filter 过滤器
从零手写实现 tomcat-12-listener 监听器
团体思路
我们通过 socket 套接字,实现最简单的服务监听。
然后直接输出一个固定的响应到页面。
套接字是个啥?
Java套接字(Socket)可以想象成一个网络通信的“管道”。就像你用水管道把水从一个地方输送到另一个地方,Java套接字则是用来在网络中传输数据的。
它允许你的Java步伐和网络中的其他步伐进行通信,无论是在同一台机器上还是活着界的另一端。
在Java中,套接字主要分为两大类:
- 服务器套接字(ServerSocket):它的作用是监听网络上的连接哀求。你可以把它想象成一个电话总机,它不主动打给别人,而是等着别人打进来。当有哀求进来时,服务器套接字就会创建一个新的通信“管道”(也就是另一个套接字),专门用来和哀求者进行数据交换。
- 客户端套接字(Socket):它的作用是主动去连接服务器套接字。就像你用电话拨打别人一样,客户端套接字会指定一个服务器的地址和端口,然后尝试创建连接。一旦连接成功,它也可以创建一个通信“管道”来发送和接收数据。
和 tomcat 有啥关系?
Tomcat作为一个Web服务器,需要和大量的客户端进行通信,比如浏览器。当浏览器哀求一个网页时,Tomcat需要接收这个哀求,并返回相应的网页数据。这个过程就需要用到Java套接字:
- 监听连接:Tomcat使用ServerSocket来监听指定端口上的HTTP哀求。当浏览器发送哀求时,Tomcat的ServerSocket就会担当这个哀求,并创建一个新的套接字来处理它。
- 数据交换:一旦连接创建,Tomcat就会通过这个套接字和浏览器进行数据交换。浏览器通过这个“管道”发送哀求,Tomcat接收哀求后,处理它,并把响应数据通过同一个“管道”发送回浏览器。
- 多线程处理:由于可能有成千上万的客户端同时哀求,Tomcat会为每个连接创建一个新的线程,这样每个哀求就可以并行处理,而不会互相干扰。
简而言之,Java套接字是Tomcat实现网络通信的核心,它允许Tomcat接收客户端的哀求,并发送响应,从而实现Web服务的功能。
v1-根本代码
核心实现
- package com.github.houbb.minicat.bs;
- import com.github.houbb.log.integration.core.Log;
- import com.github.houbb.log.integration.core.LogFactory;
- import com.github.houbb.minicat.exception.MiniCatException;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- /**
- * @author 老马啸西风
- * @since 0.1.0
- */
- public class MiniCatBootstrap {
- private static final Log logger = LogFactory.getLog(MiniCatBootstrap.class);
- /**
- * 启动端口号
- */
- private final int port;
- public MiniCatBootstrap(int port) {
- this.port = port;
- }
- public MiniCatBootstrap() {
- this(8080);
- }
- public void start() {
- logger.info("[MiniCat] start listen on port {}", port);
- logger.info("[MiniCat] visit url http://{}:{}", "127.0.0.1", port);
- try {
- ServerSocket serverSocket = new ServerSocket(port);
- while(true){
- Socket socket = serverSocket.accept();
- OutputStream outputStream = socket.getOutputStream();
- outputStream.write("Hello miniCat!".getBytes());
- socket.close();
- }
- } catch (IOException e) {
- logger.error("[MiniCat] meet ex", e);
- throw new MiniCatException(e);
- }
- }
- }
复制代码 启动测试
- MiniCatBootstrap bootstrap = new MiniCatBootstrap();
- bootstrap.start();
复制代码 日志:- [INFO] [2024-04-01 16:55:56.705] [main] [c.g.h.m.b.MiniCatBootstrap.start] - [MiniCat] start listen on port 8080
- [INFO] [2024-04-01 16:55:56.705] [main] [c.g.h.m.b.MiniCatBootstrap.start] - [MiniCat] visit url http://127.0.0.1:8080
复制代码 我们浏览器访问 http://127.0.0.1:8080,却报错了- 该网页无法正常运作127.0.0.1 发送的响应无效。
- ERR_INVALID_HTTP_RESPONSE
复制代码 为什么会报错呢?
在这个 MiniCatBootstrap 类中,服务器接收到哀求后,直接向客户端发送了 "Hello miniCat!" 字符串。
然而,HTTP 协议规定了一定的格式要求,而 "Hello miniCat!" 并不符合这些格式要求,因此客户端无法精确剖析这个响应,导致出现 "ERR_INVALID_HTTP_RESPONSE" 错误。
要修复这个题目,你需要修改 MiniCatBootstrap 类,以便生成符合 HTTP 格式的响应。
比方,你可以将 "Hello miniCat!" 包装在一个正当的 HTTP 响应中,如下所示:- String response = "HTTP/1.1 200 OK\r\n" +
- "Content-Type: text/plain\r\n" +
- "\r\n" +
- "Hello miniCat!";
- outputStream.write(response.getBytes());
复制代码 这个响应包括了 HTTP 状态行("HTTP/1.1 200 OK")、Content-Type 头部("Content-Type: text/plain")和一个空行("\r\n"),然后是 "Hello miniCat!" 字符串。
这样生成的响应就符合了 HTTP 协议的要求,客户端应该能够精确剖析它。
代码调解
我们把原来的原始字符串调解下:- outputStream.write(InnerHttpUtil.httpResp("Hello miniCat!").getBytes());
复制代码 工具类如下:- /**
- * 符合 http 标准的字符串
- * @param rawText 原始文本
- * @return 结果
- */
- public static String httpResp(String rawText) {
- String format = "HTTP/1.1 200 OK\r\n" +
- "Content-Type: text/plain\r\n" +
- "\r\n" +
- "%s";
- return String.format(format, rawText);
- }
复制代码 再次访问,就一切都正常了。
v2-代码优化+支持stop
上面的方法不支持 stop,这有点不敷优雅。
代码调解
调解后的代码实现如下:- package com.github.houbb.minicat.bs;
- import com.github.houbb.log.integration.core.Log;
- import com.github.houbb.log.integration.core.LogFactory;
- import com.github.houbb.minicat.exception.MiniCatException;
- import com.github.houbb.minicat.util.InnerHttpUtil;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.net.ServerSocket;
- import java.net.Socket;
- /**
- * @author 老马啸西风
- * @since 0.1.0
- */
- public class MiniCatBootstrap {
- private static final Log logger = LogFactory.getLog(MiniCatBootstrap.class);
- /**
- * 启动端口号
- */
- private final int port;
- /**
- * 是否运行的标识
- */
- private volatile boolean runningFlag = false;
- /**
- * 服务端 socket
- */
- private ServerSocket serverSocket;
- public MiniCatBootstrap(int port) {
- this.port = port;
- }
- public MiniCatBootstrap() {
- this(8080);
- }
- /**
- * 服务的启动
- */
- public synchronized void start() {
- if(runningFlag) {
- logger.warn("[MiniCat] server is already start!");
- return;
- }
- logger.info("[MiniCat] start listen on port {}", port);
- logger.info("[MiniCat] visit url http://{}:{}", "127.0.0.1", port);
- try {
- this.serverSocket = new ServerSocket(port);
- runningFlag = true;
- while(runningFlag){
- Socket socket = serverSocket.accept();
- OutputStream outputStream = socket.getOutputStream();
- outputStream.write(InnerHttpUtil.httpResp("Hello miniCat!").getBytes());
- socket.close();
- }
- logger.info("[MiniCat] end listen on port {}", port);
- } catch (IOException e) {
- logger.error("[MiniCat] start meet ex", e);
- throw new MiniCatException(e);
- }
- }
- /**
- * 服务的启动
- */
- public synchronized void stop() {
- if(!runningFlag) {
- logger.warn("[MiniCat] server is not start!");
- return;
- }
- try {
- if(this.serverSocket != null) {
- serverSocket.close();
- }
- this.runningFlag = false;
- logger.info("[MiniCat] stop listen on port {}", port);
- } catch (IOException e) {
- logger.error("[MiniCat] stop meet ex", e);
- throw new MiniCatException(e);
- }
- }
- }
复制代码 我们定义一个 runingFlag 变量标识,stop 之后就可以根据这个属性判断是否继续执行。
测试代码
我们预期服务启动 30S 之后,然后关闭。
代码如下:- MiniCatBootstrap bootstrap = new MiniCatBootstrap();
- bootstrap.start();TimeUnit.SECONDS.sleep(30);bootstrap.stop();
复制代码 这里会按照我们预期执行吗?为什么?
测试结果
测试日志:- [DEBUG] [2024-04-01 17:23:55.012] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter.
- [INFO] [2024-04-01 17:23:55.014] [main] [c.g.h.m.b.MiniCatBootstrap.start] - [MiniCat] start listen on port 8080
- [INFO] [2024-04-01 17:23:55.015] [main] [c.g.h.m.b.MiniCatBootstrap.start] - [MiniCat] visit url http://127.0.0.1:8080
复制代码 我们等待好久,也并没有比及服务关闭。
为什么?
纵然在修改后的代码中添加了 stop() 方法来制止服务器,但是 start() 方法仍旧会在一个无限循环中监听连接哀求,导致主线程被阻塞。
这是因为 start() 方法中的 while 循环会一直执行,直到 stop() 方法被调用将 runningFlag 设置为 false。
要办理这个题目,可以将服务器的监听逻辑放在一个单独的线程中执行,这样 start() 方法就可以立即返回,不会阻塞主线程。
v3-办理主线程阻塞题目
思路
我们把主线程运行放到一个异步线程,不去阻塞主线存。
实现
启动测试
- MiniCatBootstrap bootstrap = new MiniCatBootstrap();
- bootstrap.start();System.out.println("main START sleep");TimeUnit.SECONDS.sleep(10);System.out.println("main END sleep");bootstrap.stop();
复制代码 日志如下:- main START sleep
- [INFO] [2024-04-02 09:03:41.604] [Thread-0] [c.g.h.m.b.MiniCatBootstrap.startSync] - [MiniCat] start listen on port 8080
- [INFO] [2024-04-02 09:03:41.604] [Thread-0] [c.g.h.m.b.MiniCatBootstrap.startSync] - [MiniCat] visit url http://127.0.0.1:8080
- main END sleep
- [INFO] [2024-04-02 09:03:51.592] [main] [c.g.h.m.b.MiniCatBootstrap.stop] - [MiniCat] stop called!
- [ERROR] [2024-04-02 09:03:51.592] [Thread-0] [c.g.h.m.b.MiniCatBootstrap.startSync] - [MiniCat] start meet ex
- java.net.SocketException: socket closed
- at java.net.DualStackPlainSocketImpl.accept0(Native Method)
- at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:127)
- at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:535)
- at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:189)
- at java.net.ServerSocket.implAccept(ServerSocket.java:545)
- at java.net.ServerSocket.accept(ServerSocket.java:513)
- at com.github.houbb.minicat.bs.MiniCatBootstrap.startSync(MiniCatBootstrap.java:74)
- at com.github.houbb.minicat.bs.MiniCatBootstrap$1.run(MiniCatBootstrap.java:49)
- at java.lang.Thread.run(Thread.java:750)
- Exception in thread "Thread-0" com.github.houbb.minicat.exception.MiniCatException: java.net.SocketException: socket closed
- at com.github.houbb.minicat.bs.MiniCatBootstrap.startSync(MiniCatBootstrap.java:83)
- at com.github.houbb.minicat.bs.MiniCatBootstrap$1.run(MiniCatBootstrap.java:49)
- at java.lang.Thread.run(Thread.java:750)
- Caused by: java.net.SocketException: socket closed
- at java.net.DualStackPlainSocketImpl.accept0(Native Method)
- at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:127)
- at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:535)
- at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:189)
- at java.net.ServerSocket.implAccept(ServerSocket.java:545)
- at java.net.ServerSocket.accept(ServerSocket.java:513)
- at com.github.houbb.minicat.bs.MiniCatBootstrap.startSync(MiniCatBootstrap.java:74)
- ... 2 more
- [INFO] [2024-04-02 09:03:51.613] [main] [c.g.h.m.b.MiniCatBootstrap.stop] - [MiniCat] stop listen on port 8080
- Process finished with exit code 0
复制代码 已经可以正常的关闭。
开源地址
mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)
开源地址:https://github.com/houbb/minicat
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |