【学习笔记】手写 Tomcat 八

目录

一、NIO

1. 创建 Tomcat NIO 类

2. 启动 Tomcat

3. 测试

二、解析请求信息

三、响应数据

创建响应类

修改调用的响应类

四、完整代码

五、测试

六、总结

七、获取全部用户的功能

POJO

生成 POJO

1. 在 Dao 层定义接口

2. 获取用户数据

3. 在 Service 层定义接口

4. Service 层的实现方法

5. 创建 Servlet

6. 测试

八、作业

优化NIO


一、NIO

Non-Blocking I/O,非阻塞IO。我们之前使用的是 BIO 阻塞IO

NIO 是同步非阻塞的,服务器的实现模式是一个线程处理多个连接 

关于 NIO ,可以看看这位博主写的文章 Java NIO 详解-CSDN博客 

1. 创建 Tomcat NIO 类

package com.shao.net;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TomcatNIO {

    public TomcatNIO() {

        // 1. 创建通信管道
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();

            // 2. 绑定端口
            ssc.bind(new InetSocketAddress(8080));

            // 3. 配置非阻塞通信管道
            ssc.configureBlocking(false);

            // 4. 创建一个选择器
            Selector selector = Selector.open();

            // 5. 将通信管道注册到选择器上,监听客户端连接请求的事件
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            // 循环监听客户端请求并处理相应事件
            while (true) {
                System.out.println("等待连接...");
                // 6. 从 selectors 中选择并返回已就绪的通道数
                int number = selector.select();
                if (number == 0) continue;

                // 7. 这些 SelectionKey 对象代表了就绪的通道及其相应的注册事件。
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext()) {
                    SelectionKey key = it.next();

                    // 8. 判断就绪事件类型
                    if (key.isAcceptable()) {
                        System.out.println("有客户端连接了...");

                        // 1. 接受新的客户端连接
                        SocketChannel sc = ssc.accept();

                        // 2. 设置连接的通道为非阻塞模式
                        sc.configureBlocking(false);

                        // 3. 将新连接的通道注册到选择器上,监听读事件
                        sc.register(selector, SelectionKey.OP_READ);

                        System.out.println("客户端连接成功");

                    } else if (key.isReadable()) {
                        // 读事件

                        // 1. 创建缓冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);

                        // 2. 将 key 关联的通道 (channel) 转换为 SocketChannel 类型。转换后的 socketChannel 可用于进行网络读写操作。
                        SocketChannel socketChannel = (SocketChannel) key.channel();

                        // 3. 读取数据到缓冲区
                        int read = socketChannel.read(buffer);

                        if (read > 0) {
                            // 获取缓冲区的数据
                            byte[] array = buffer.array();

                            // 转成字符串
                            String msg = new String(array, 0, read);

                            // 将请求信息进行 URL 解码,然后使用 UTF-8 进行编码
                            String message = URLDecoder.decode(msg, "UTF-8");

                            System.out.println("客户端发送了:" + message);

                            // 清空缓冲区
                            buffer.clear();

                            // 响应数据
                            String content = "OK";
                            String HttpResponse = "HTTP/1.1 200 OK\r\n" +
                                    "Content-Type: text/html;charset=utf-8\r\n" +
                                    "Content-Length: " + content.getBytes().length + "\r\n" +
                                    "\r\n" +
                                    content;

                            // 写入到缓冲区
                            buffer.put(HttpResponse.getBytes());

                            // 将缓冲区的界限设置为当前位置,然后再把当前位置重置为0
                            buffer.flip();

                            // 响应数据到客户端
                            socketChannel.write(buffer);

                        } else if (read == -1) {

                            // 关闭通道
                            socketChannel.close();

                            // 取消 key 关联的通道在 selector 上的注册
                            key.cancel();
                        }
                    }


                    // 移除已经处理的 SelectionKey,防止下次循环再处理这个键
                    it.remove();

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. 启动 Tomcat

在入口类启动 NIO 的 Tomcat

3. 测试

二、解析请求信息

因为请求信息和之前是一样的,所以可以使用 HttpRequest 解析类

三、响应数据

创建响应类

因为响应数据的方式不一样,所以需要创建一个 NIO 方式的响应类

package com.shao.net;

import com.shao.Servlet.BaseServlet;
import com.shao.Utils.ServletByAnnoUtil;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class HttpResponseNIO {

    /**
     * 连接的通道
     */
    private SocketChannel sc;

    /**
     * 缓冲区
     */
    private ByteBuffer buffer;

    /**
     * 解析类的对象
     */
    private HttpRequest httpRequest;

    public HttpResponseNIO(SocketChannel sc, ByteBuffer buffer, HttpRequest httpRequest) {
        this.sc = sc;
        this.buffer = buffer;
        this.httpRequest = httpRequest;
    }

    public void response(String filePath) {
        //判断请求的是否为静态文件
        if (StaticResourceHandler.isLikelyStaticResource(httpRequest.getRequestModule())) {
            // 获取静态资源一般是 GET 请求方法
            if (httpRequest.getRequestMethod().equals("GET")) {
                // 响应静态资源
                responseStaticResource(filePath);
            }
        } else {
            // 处理动态请求
            System.out.println("请求动态资源");

            // 获取 Servlet 对象,参数是请求的模块名
            BaseServlet servlet = ServletByAnnoUtil.getServletClass(httpRequest.getRequestModule());

            // 如果没有找到对应的 Servlet ,返回 404
            if (servlet == null) {
                responseStaticResource("webs/pages/not_Found404.html");
                return;
            }
            // 调用 service 方法
            servlet.service(httpRequest, this);
        }
    }

    /**
     * 响应静态资源
     */
    private void responseStaticResource(String filePath) {
        // 读取文件
        byte[] fileContents = StaticResourceHandler.getFileContents(filePath);

        // 判断文件是否存在,不存在则返回 404 的页面
        if (fileContents == null) {
            try {
                FileInputStream fis = new FileInputStream("webs/pages/not_Found404.html");

                fileContents = new byte[fis.available()];

                fis.read(fileContents);

                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 响应协议
        String protocol = httpRequest.getRequestProtocol();
        // 文件媒体类型
        String fileMimeType = StaticResourceHandler.getFileMimeType(filePath);

        // 清空缓冲区
        buffer.clear();

        // 写入数据到缓冲区
        buffer.put((protocol + " 200 OK\r\n").getBytes());
        buffer.put(("Content-Type: " + fileMimeType + "\r\n").getBytes());
        buffer.put(("Content-Length: " + fileContents.length + "\r\n").getBytes());
        buffer.put(("\r\n").getBytes());
        buffer.put(fileContents);

        // 将缓冲区的界限设置为当前位置,然后再把当前位置重置为0
        buffer.flip();

        try {
            // 响应数据到客户端
            sc.write(buffer);
            System.out.println("响应成功");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void send(byte[] content) {
        // 获取请求协议
        String protocol = httpRequest.getRequestProtocol();

        // 清空缓冲区
        buffer.clear();

        // 往缓冲区写入数据
        buffer.put((protocol + " 200 OK\r\n").getBytes());
        buffer.put(("Content-Type: text/html;charset=utf-8\r\n").getBytes());
        buffer.put(("Content-Length: " + content.length + "\r\n").getBytes());
        buffer.put("\r\n".getBytes());
        buffer.put(content);

        // 设置缓冲区的界限为当前指针的位置,然后把指针指向缓冲区的起始位置
        buffer.flip();

        try {

            // 响应数据
            sc.write(buffer);
            System.out.println("响应成功");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

修改调用的响应类

四、完整代码

package com.shao.net;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TomcatNIO {
    public TomcatNIO() {

        // 1. 创建通信管道
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();

            // 2. 绑定端口
            ssc.bind(new InetSocketAddress(8080));

            // 3. 配置非阻塞通信管道
            ssc.configureBlocking(false);

            // 4. 创建一个选择器
            Selector selector = Selector.open();

            // 5. 将通信管道注册到选择器上,监听客户端连接请求的事件
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            // 循环监听客户端请求并处理相应事件
            while (true) {
                System.out.println("等待连接...");
                // 6. 从 selectors 中选择并返回已就绪的通道数
                int number = selector.select();
                if (number == 0) continue;

                // 7. 这些 SelectionKey 对象代表了就绪的通道及其相应的注册事件。
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext()) {
                    SelectionKey key = it.next();

                    // 8. 判断就绪事件类型
                    if (key.isAcceptable()) {
                        System.out.println("有客户端连接了...");

                        // 1. 接受新的客户端连接
                        SocketChannel sc = ssc.accept();

                        // 2. 设置连接的通道为非阻塞模式
                        sc.configureBlocking(false);

                        // 3. 将新连接的通道注册到选择器上,监听读事件
                        sc.register(selector, SelectionKey.OP_READ);

                        System.out.println("客户端连接成功");

                    } else if (key.isReadable()) {
                        // 读事件

                        // 1. 创建缓冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);

                        // 2. 将 key 关联的通道 (channel) 转换为 SocketChannel 类型。转换后的 socketChannel 可用于进行网络读写操作。
                        SocketChannel socketChannel = (SocketChannel) key.channel();

                        // 3. 读取数据到缓冲区
                        int read = socketChannel.read(buffer);

                        if (read > 0) {
                            // 获取缓冲区的数据
                            byte[] array = buffer.array();

                            // 转成字符串
                            String msg = new String(array, 0, read);

                            // 将请求信息进行 URL 解码,然后使用 UTF-8 进行编码
                            String message = URLDecoder.decode(msg, "UTF-8");

                            // 调用 HttpRequest 类解析请求信息
                            HttpRequest httpRequest = new HttpRequest(message);

                            // 拼接请求的静态资源的路径
                            String filePath = "/webs" + httpRequest.getRequestModule();

                            // 创建响应对象
                            HttpResponseNIO httpResponseNIO = new HttpResponseNIO(socketChannel, buffer, httpRequest);

                            // 响应数据到客户端
                            httpResponseNIO.response(filePath);

                        } else if (read == -1) {

                            // 关闭通信通道
                            socketChannel.close();

                            // 取消 key 关联的通道在 selector 上的注册
                            key.cancel();
                        }
                    }

                    // 9. 移除已经处理的 SelectionKey,防止下次循环再处理这个键
                    it.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

五、测试

六、总结

七、获取全部用户的功能

POJO

因为获取的用户数据需要封装,需要一个类和数据库的字段一一对应,这就是 对象关系映射(ORM) ,这个类可以称为 POJO

生成 POJO

1. 在 Dao 层定义接口

2. 获取用户数据

public ArrayList<Users> GetAllUser() {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet resultSet = null;

        ArrayList<Users> users = new ArrayList<>();

        try {
            // 3. 从连接池获取一个数据库连接
            connection = DBConnectPool.getInstance().getConnection();

            // 4. 获取可执行对象
            // 定义 SQL 语句
            String SQL = "select id, account, name, password, money from train.users";
            pstmt = connection.prepareStatement(SQL);

            // 5. 执行sql语句,获取结果集
            resultSet = pstmt.executeQuery();

            // 6. 结果处理
            while (resultSet.next()) {

                // 获取一行数据,封装到对象中
                Users user = new Users();

                user.setId(resultSet.getLong("id"));
                user.setAccount(resultSet.getString("account"));
                user.setName(resultSet.getString("name"));
                user.setPassword(resultSet.getString("password"));
                user.setMoney(resultSet.getDouble("money"));

                // 添加到集合中
                users.add(user);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBConnectPool.getInstance().releaseConnection(connection);
            DBConnectUtil.releaseSource(pstmt, resultSet);
        }
        return users;
    }

3. 在 Service 层定义接口

4. Service 层的实现方法

    public responseDTO GetAllUser() {

        // 调用 Dao 层获取数据
        ArrayList<Users> users = userDao.GetAllUser();

        return new responseDTO(200, users, "获取成功", users.size());
    }

5. 创建 Servlet

package com.shao.Servlet;

import com.alibaba.fastjson2.JSON;
import com.shao.Annotation.MyServlet;
import com.shao.Service.ServiceFactory;
import com.shao.Service.UserService;
import com.shao.Utils.responseDTO;
import com.shao.net.HttpRequest;
import com.shao.net.HttpResponseNIO;

@MyServlet("/GetAllUser")
public class GetAllUserServlet extends BaseServlet {
    responseDTO responseDTO = null;
    @Override
    void doGet(HttpRequest httpRequest, HttpResponseNIO httpResponse) {
        // 获取实例
        UserService userService = ServiceFactory.getUserService();

        // 调用获取所有用户的方法
        responseDTO = userService.GetAllUser();

        // 响应数据
        httpResponse.send(JSON.toJSONBytes(responseDTO));
    }
    @Override
    void doPost(HttpRequest httpRequest, HttpResponseNIO httpResponse) {
        responseDTO = new responseDTO(400, null, "不支持POST提交方法");
        httpResponse.send(JSON.toJSONBytes(responseDTO));
    }
}

6. 测试

八、作业

优化NIO

高并发的场景下,现在的NIO配置无法及时处理,如何解决?

如果客户端发送的数据很多,如何分批次读取数据?

到目前为止,我们学习了 BIO和NIO网络通信模块、HttpRequest、HttpResponse、线程池、任务队列、线程任务对象、Servlet、业务逻辑处理 Service 、Dao 、数据库连接池、POJO、DTO、注解等。这些内容组合起来就是一个简单的框架

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

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

相关文章

ArcGIS与ArcGIS Pro去除在线地图服务名单

我们之前给大家分享了很多在线地图集&#xff0c;有些地图集会带有制作者信息&#xff0c;在布局制图的时候会带上信息影响出图美观。 一套GIS图源集搞定&#xff01;清新规划底图、影像图、境界、海洋、地形阴影图、导航图 比如ArcGIS&#xff1a; 比如ArcGIS Pro&#xff1a…

.Net 基于IIS部署blazor webassembly或WebApi

1.安装IIS(若安装&#xff0c;请忽略) 选择:控制面板–>程序–>程序和功能 选择:启动或关闭Windows功能&#xff0c;勾选相关项&#xff0c;再点击确定即可。 2.安装Hosting Bundle 以.net6为例&#xff0c;点击连接https://dotnet.microsoft.com/en-us/download/dot…

zabbix7.0创建自定义模板的案例详解(以监控httpd服务为例)

前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) 创建模板 模板组直接写一个新的&#xff0c;不用选择 通过名称查找模板&#xf…

详解CSS中的伪元素

4.3 伪元素 可以把样式应用到文档树中根本不存在的元素上。 ::first-line 文本中的第一行 ::first-letter 文本中的第一个字母 ::after 元素之后添加 ::before 元素之前 代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8&q…

测试用例的举例

1. 基于测试公式设计测试用例 通过功能&#xff0c;性能&#xff0c;安全性&#xff0c;界面&#xff0c;安全性&#xff0c;易用&#xff0c;兼容对于一个水杯进行测试用例的设计&#xff1b; 对于一个软件的测试用例设计&#xff1a; 功能&#xff1a;软件本质上能够用来干什…

本科生已不够 AI公司雇佣各领域专家训练大模型

9月29日消息&#xff0c;人工智能模型的性能在很大程度上依赖于其训练数据的质量。传统方法通常是雇用大量低成本劳动力对图像、文本等数据进行标注&#xff0c;以满足模型训练的基本需求。然而&#xff0c;这种方式容易导致模型在理解和生成信息时出现“幻觉”现象&#xff0c…

【递归】11. leetcode 129 求根节点到叶节点数字之和

1 题目描述 题目链接&#xff1a; 求根节点到叶节点数字之和 2 解答思路 第一步&#xff1a;挖掘出相同的子问题 &#xff08;关系到具体函数头的设计&#xff09; 第二步&#xff1a;只关心具体子问题做了什么 &#xff08;关系到具体函数体怎么写&#xff0c;是一个宏观…

追随 HarmonyOS NEXT,Solon v3.0 将在10月8日发布

Solon &#xff08;开放原子开源基金会&#xff0c;孵化项目&#xff09;原计划10月1日发布 v3.0 正式版。看到 HarmonyOS NEXT 将在 10月8日启用公测&#xff0c;现改为10月8日发布以示庆贺。另外&#xff0c;Solon 将在2025年启动“仓颉”版开发&#xff08;届时&#xff0c;…

数据仓库的建设——从数据到知识的桥梁

数据仓库的建设——从数据到知识的桥梁 前言数据仓库的建设 前言 企业每天都在产生海量的数据&#xff0c;这些数据就像无数散落的珍珠&#xff0c;看似杂乱无章&#xff0c;但每一颗都蕴含着潜在的价值。而数据仓库&#xff0c;就是那根将珍珠串起来的线&#xff0c;它能够把…

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯&#xff08;IM&#xff09;系统和实时通信应用中&#xff0c;WebSocket作为一种高效的双向通信协议&#xff0c;得到了广泛应用。然而&#xff0c;在实际使用中&#xff0c;如何确保消息的可靠传输…

解决Windows远程桌面 “为安全考虑,已锁定该用户账户,原因是登录尝试或密码更改尝试过多,请稍后片刻再重试,或与系统管理员或技术支持联系“问题

根本原因就是当前主机被通过远程桌面输入了过多的错误密码&#xff0c;被系统锁定。这种情况多数是你的服务器远程桌面被人试图攻击了&#xff0c;不建议取消系统锁定策略。如果阿里云或者腾讯云主机&#xff0c;只需要在管理后台通过管理终端或者VNC登陆一次&#xff0c;锁定即…

哈希表(HashMap、HashSet)

文章目录 一、 什么是哈希表二、 哈希冲突2.1 为什么会出现冲突2.2 如何避免出现冲突2.3 出现冲突如何解决 三、模拟实现哈希桶/开散列&#xff08;整型数据&#xff09;3.1 结构3.2 插入元素3.3 获取元素 四、模拟实现哈希桶/开散列&#xff08;泛型&#xff09;4.1 结构4.2 插…

DC00025【含论文】基于协同过滤推荐算法springboot视频推荐管理系统

1、项目功能演示 DC00025【含文档】基于springboot短视频推荐管理系统协同过滤算法视频推荐系统javaweb开发程序设计vue 2、项目功能描述 短视频推荐系统分为用户和系统管理员两个角色 2.1 用户角色 1、用户登录、用户注册 2、视频中心&#xff1a;信息查看、视频收藏、点赞、…

1.1.4 计算机网络的分类

按分布范围分类&#xff1a; 广域网&#xff08;wan&#xff09; 城域网&#xff08;man&#xff09; 局域网&#xff08;lan&#xff09; 个域网&#xff08;pan&#xff09; 注意&#xff1a;如今局域网几乎采用“以太网技术实现”&#xff0c;因此“以太网”几乎成了“局域…

Python核心知识:pip使用方法大全

什么是 pip&#xff1f; pip 是 Python 的包管理工具&#xff0c;允许用户安装、升级和管理 Python 的第三方库和依赖。它极大地简化了开发过程&#xff0c;使开发者可以轻松地获取并安装所需的软件包。pip 已成为 Python 项目中最常见的包管理工具&#xff0c;并且自 Python …

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】 一、上篇回顾二、项目准备2.1 准备模板项目2.2 支持计时功能2.3 配置UART4引脚2.4 支持printf重定向到UART42.5 支持printf输出浮点数2.6 支持printf不带\r的换行2.7 支持ccache编译缓存 三、TFLM集成3.1 添加tfli…

Ceph RocksDB 深度调优

介绍 调优 Ceph 可能是一项艰巨的挑战。在 Ceph、RocksDB 和 Linux 内核之间&#xff0c;实际上有数以千计的选项可以进行调整以提高存储性能和效率。由于涉及的复杂性&#xff0c;比较优的配置通常分散在博客文章或邮件列表中&#xff0c;但是往往都没有说明这些设置的实际作…

C# 相等性检测方法差异分析(==,Equals,ReferenceEquals)

先给结论&#xff1a; 对于每种类型创建2个一样的数据&#xff0c;比较结果如下表所示&#xff1a; 数据类型EqualsReferenceEqualsint(值类型)√√引用类型引用类型&#xff08;带override&#xff09;以operator 实现为准以Equals覆写为准struct必须实现操作符√struct&…

Android 12系统源码_输入系统(三)输入事件的加工和分发

前言 上一篇文章我们具体分析了InputManagerService的构造方法和start方法&#xff0c;知道IMS的start方法经过层层调用&#xff0c;最终会触发Navite层InputDispatcher的start方法和InputReader的start方法。InputDispatcher的start方法会启动一个名为InputDispatcher的线程&…

基于深度学习的点云处理模型PointNet++学习记录

前面我们已经学习了Open3D&#xff0c;并掌握了其相关应用&#xff0c;但我们也发现对于一些点云分割任务&#xff0c;我们采用聚类等方法的效果似乎并不理想&#xff0c;这时&#xff0c;我们可以想到在深度学习领域是否有相关的算法呢&#xff0c;今天&#xff0c;我们便来学…