从零手写实现 tomcat-03-基本的 socket 实现

创作缘由

平时使用 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中,套接字主要分为两大类:

  1. 服务器套接字(ServerSocket):它的作用是监听网络上的连接请求。你可以把它想象成一个电话总机,它不主动打给别人,而是等着别人打进来。当有请求进来时,服务器套接字就会创建一个新的通信“管道”(也就是另一个套接字),专门用来和请求者进行数据交换。

  2. 客户端套接字(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-解决主线程阻塞问题

思路

我们把主线程运行放到一个异步线程,不去阻塞主线存。

实现

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;

/**
 * @since 0.1.0
 * @author 老马啸西风
 */
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() {
        // 引入线程池
        Thread serverThread = new Thread(new Runnable() {
            @Override
            public void run() {
                startSync();
            }
        });

        // 启动
        serverThread.start();
    }

    /**
     * 服务的启动
     */
    public void startSync() {
        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 && !serverSocket.isClosed()){
                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 void stop() {
        logger.info("[MiniCat] stop called!");

        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);
        }
    }

}

启动测试

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

已经可以正常的关闭。

开源地址

 /\_/\  
( o.o ) 
 > ^ <

mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)

开源地址:https://github.com/houbb/minicat

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/610148.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RAG技术简介

相关文档&#xff1a; 论文链接&#xff1a; https://arxiv.org/abs/2005.11401 课程链接&#xff1a; Tutorial/huixiangdou at camp2 InternLM/Tutorial GitHub 视频链接&#xff1a; 茴香豆&#xff1a;搭建你的 RAG 智能助理_哔哩哔哩_bilibili RAG是一种在LLM中广泛使…

echarts指标盘属性概括

echarts指标盘属性概括 代码 有模拟数据可以直接使用const options {animation: true,title: {top: "35%",left: "center",// text: "单元测试覆盖度", // 主标题itemGap: 15,textStyle: {// 主标题样式color: "#666666",fontSize:…

Spring MVC分页示例

Spring MVC分页示例 分页用于在不同部分显示大量记录。在这种情况下&#xff0c;我们将在一页中显示10、20或50条记录。对于其余记录&#xff0c;我们提供链接。 我们可以在Spring MVC中简单地创建分页示例。在此分页示例中&#xff0c;我们使用MySQL数据库来获取记录。 创建…

MySQL索引优化(超详细)篇章2--索引调优

目录 1.索引失效状况2.性能分析3.表的索引信息--调整索引顺序4.删除冗余索引5.最佳左前缀法则5.1下面是一个实际的例子来说明这个概念&#xff1a; 6.数据长度和索引长度占用空间比较 1.索引失效状况 MySQL索引失效通常指的是查询语句无法有效地利用索引&#xff0c;而导致全表…

为什么说HTTPS比HTTP安全? HTTPS是如何保证安全的?

一、安全特性 在上篇文章中&#xff0c;我们了解到HTTP在通信过程中&#xff0c;存在以下问题&#xff1a; 通信使用明文&#xff08;不加密&#xff09;&#xff0c;内容可能被窃听不验证通信方的身份&#xff0c;因此有可能遭遇伪装而HTTPS的出现正是解决这些问题&#xff0c…

基于springboot + vue 实现的简易博客系统

项目效果图 登陆页面 文章列表 发表文章 用户管理 栏目管理 数据统计 后端技术栈后端主要采用了&#xff1a; 1.SpringBoot 2.SpringSecurity 3.MyBatis 4.部分接口遵循Restful风格 5.MySQL 前端技术栈前端主要采用了&#xff1a; 1.Vue 2.axios 3.Elemen…

ETL如何执行Java脚本

ETLCloud提供了执行 Java 脚本的方式&#xff0c;让用户能够灵活地处理数据并实现各种复杂的数据处理任务。 ETLCloud在数据处理领域的应用优势主要体现在以下几个方面&#xff1a; 灵活性&#xff1a;通过执行Java脚本&#xff0c;用户能够灵活定制数据处理逻辑&#xff0c;满…

C语言---使用共用体将double型经纬度存储到无符号数组中

1.在上报经纬度时由于数据协议限制需要将double型数据存储到无符号数组中&#xff0c;下边是写了一个简单C程序进行验证&#xff1b; 2.代码示例如下 #include <stdio.h> typedef union {float data;unsigned char arr[4]; } my_data;int main() {my_data test_data {…

IDEA HTTP Client 插件配置空密码的 Request

最近在测试一些 rest api 的时候&#xff0c;发现 IDEA 的 HTTP Client 很好用。对比 postman 更加的轻量&#xff0c;可以满足一些简单的 HTTP 请求测试。这里主要记录下&#xff0c;当用户名没有设置密码时&#xff0c;我们该如何配置这个 HTTP Client 的 Request 文件&#…

综合内容运营实习生实习体验报告怎么写?

分享一个笔灵ai生成的综合内容运营的实习体验报告&#xff0c;有别的岗位需要的可以自己去网站使用 实习体验报告AI写作助手 | AI文章智能生成器 - 笔灵AI写作 实习体验报告 尊敬的领导&#xff1a; 您好&#xff01;我很荣幸有机会向您提交我的实习体验报告。在过去的几个月…

wpf中的图标字体和android的矢量图vector

1.背景 在安卓程序中如下截图所示中&#xff0c;在drawable文件夹下有如下图片文件。 这是一个xml文件&#xff0c;打开后看到Vector节点下paht中有一个pathData属性有一串带字母数字和"."点的数据。这些是什么呢&#xff1f; <vector xmlns:android"http:…

14个项目带你熟练学握AI2G免费分享|一门让你掌握人工智能能力的实操课(送工具)利用Midjourney

目录 1-一门让你掌握人工智能能力的实操课&#xff08;送工具&#xff09;.mp4 10-成为UI设计师&#xff1a;利用Midjourney进行页面UI设计.mp4 11-做室内设计师&#xff1a;利用Midjourney产出家居、室内设计方案.mp4 12-故事绘本&#xff1a;利用Midjouney准备课件、故事…

PCIe协议之-TLP路由基础

✨前言&#xff1a; 在PCI Express (PCIe) 技术中&#xff0c;数据包的路由方式对于确保信息能够高效、准确地传送至目标设备至关重要。PCIe定义了几种路由方式&#xff0c;主要有以下几种。 &#x1f31f;地址路由&#xff08;Address Based Routing&#xff09; 这是最基本…

自动驾驶系统中的数据闭环:挑战与前景

目录 自动驾驶概况 1.1自动驾驶分级 1.2自动驾驶国内发展 ​1.3自动驾驶架构模型 数据闭环的意义 2.1 搜集corner case的数据 2.2 提高模型的泛化能力 2.3 驱动算法迭代 数据闭环落地的痛点及对策 3.1 数据采集和使用的合规性问题 3.2 数据确权问题 3.3 数据采集…

【经验总结】 常用的模型优化器

优化器是一种用于优化模型权重和偏差的算法&#xff0c;它根据训练数据更新模型参数&#xff0c;以模型的预测结果更加准确。 1. 常见的优化器 SGD&#xff08;Stochastic Gradient Descent&#xff09;&#xff1a;SGD是一种基本的优化算法&#xff0c;它在每次迭代中随机选择…

揭秘Ping32如何实现上网行为监控

企业上网行为管理软件在现代企业管理中扮演着举足轻重的角色。它不仅能够监控和记录员工的上网行为&#xff0c;还能有效防止数据泄露和不当使用&#xff0c;从而保障企业的信息安全。 一、Ping32上网监控软件的具体功能包括&#xff1a; 1.网页浏览监控&#xff1a;对Chrome…

jvm面试题30问

什么是JVM的跨平台&#xff1f; 什么是JVM的语言无关性&#xff1f; 什么是JVM的解释执行 什么是JIT? JIT&#xff1a;在Java编程语言和环境中&#xff0c;即时编译器&#xff08;JIT compiler&#xff0c;just-in-time compiler&#xff09;是一个把Java的字节码&#xff08;…

流量卡就该这么选,用起来性价比真的超高!

很多朋友会私信小编&#xff0c;让小编给大家推荐几款流量卡&#xff0c;在这里小编告诉大家&#xff0c;流量卡可以推荐&#xff0c;但是每个人的喜好不同&#xff0c;小编也忙不过来&#xff0c;今天&#xff0c;小编整理了一篇选购指南&#xff0c;大家可以参考选择&#xf…

2024 B2B企业出海营销白皮书(展会篇)

来源&#xff1a;科特勒&微吼 根据36氪研究院发布的《2023-2024年中国企业出海发展研究报告》中指出&#xff0c;随着全球化浪潮席卷以及中国智造的崛起&#xff0c;中国企业出海主力从过去的低附加值行业逐步扩展至信息技术、先进制造、医疗健康、汽车交通、新消费等附加…

106短信平台疑难解答:为何手机正常却收不到短信?

当您使用群发短信平台发送消息时&#xff0c;有时尽管系统提示发送成功&#xff0c;但手机却未能收到短信。这背后可能隐藏着一些不为人知的原因。 首先&#xff0c;我们要明确&#xff0c;在正常情况下&#xff0c;只要手机状态正常&#xff0c;都应该能够接收到短信。然而&am…