【学习笔记】手写 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());// 将缓冲区的界限设置为当前位置,然后再把当前位置重置为0buffer.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 ,返回 404if (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);// 将缓冲区的界限设置为当前位置,然后再把当前位置重置为0buffer.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;@Overridevoid doGet(HttpRequest httpRequest, HttpResponseNIO httpResponse) {// 获取实例UserService userService = ServiceFactory.getUserService();// 调用获取所有用户的方法responseDTO = userService.GetAllUser();// 响应数据httpResponse.send(JSON.toJSONBytes(responseDTO));}@Overridevoid 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、注解等。这些内容组合起来就是一个简单的框架
相关文章:
【学习笔记】手写 Tomcat 八
目录 一、NIO 1. 创建 Tomcat NIO 类 2. 启动 Tomcat 3. 测试 二、解析请求信息 三、响应数据 创建响应类 修改调用的响应类 四、完整代码 五、测试 六、总结 七、获取全部用户的功能 POJO 生成 POJO 1. 在 Dao 层定义接口 2. 获取用户数据 3. 在 Service 层定…...
24年九月份生活随笔
九月份最后一天,烈士纪念日。 上午看了一会儿直播,庄重的仪式,铭记先辈为新中国抛头颅洒热血,当今盛世,如您所愿。 郑州马拉松官方通告,今天十点公布直通,中签,候补结果。 看完直…...
[含文档+PPT+源码等]精品大数据项目-基于Django实现的高校图书馆智能推送系统的设计与实现
大数据项目——基于Django实现的高校图书馆智能推送系统的设计与实现背景,可以从以下几个方面进行详细阐述: 一、信息技术的发展背景 随着信息技术的飞速发展和互联网的广泛普及,大数据已经成为现代社会的重要资源。在大数据背景下…...
Leecode刷题之路第七天之整数反转
题目出处 07-整数反转 题目描述 个人解法 思路: 1.将整数转换为字符串 2.倒序输出字符串 3.兼容负数case 代码示例:(Java) public int reverse(int x) {Integer integer new Integer(x);String s integer.toString();Strin…...
SpringBoot项目 | 瑞吉外卖 | 短信发送验证码功能改为免费的邮箱发送验证码功能 | 代码实现
0.前情提要 之前的po已经说了单独的邮箱验证码发送功能怎么实现: https://blog.csdn.net/qq_61551948/article/details/142641495 这篇说下如何把该功能整合到瑞吉项目里面,也就是把原先项目里的短信发送验证码的功能改掉,改为邮箱发送验证…...
Windows暂停更新
目录 前言注册表设定参考 前言 不想Windows自动更新,同时不想造成Windows商店不可用,可以采用暂停更新的方案。 但是通过这里设定的时间太短了,所以我们去注册表设定。 注册表设定 win r 输入 regedit进入注册表 HKEY_LOCAL_MACHINE\SOFT…...
alpine安装docker踩坑记
文章目录 前言错误场景正确操作最后 前言 你好,我是醉墨居士,最近使用alpine操作系统上docker遇到了一些错误,尝试解决之后就准备输出一篇博客,帮助有需要的后人能够少踩坑,因为淋过雨所以想给别人撑伞 错误场景 我…...
使用openpyxl轻松操控Excel文件
目录 1. openpyxl 简介2. 安装与快速入门2.1 安装 openpyxl2.2 快速创建一个 Excel 文件2.3 读取 Excel 文件 3. openpyxl 的核心概念3.1 工作簿(Workbook)3.2 工作表(Worksheet)3.3 单元格(Cell)3.4 行与列…...
指定PDF或图片多个识别区域,识别区域文字,并批量对PDF或图片文件改名
常见场景 用户有大量图片/PDF文件,期望能按照图片/PDF中的某些文字对图片/PDF文件重命名。期望工具可以批量处理、离线识别(保证数据安全性)。手工操作麻烦。具体场景:用户有工程现场照片,订单,简历等PDF或…...
Web3中的跨链技术:实现无缝连接的挑战
Web3的到来为互联网带来了去中心化的愿景,而跨链技术则是实现这一愿景的关键。跨链技术旨在解决不同区块链之间的互操作性问题,使得用户和应用能够在多个区块链网络之间无缝地传输数据和价值。尽管这一技术具有广阔的前景,但在实现过程中仍面…...
词袋(Bag of Words, BoW)
词袋(Bag of Words, BoW)模型详解 词袋(BoW)是一种用于文本处理的特征提取方法,常用于自然语言处理(NLP)任务中。在BoW模型中,文本被表示为一个词的无序集合,而忽略了词…...
HTTP Status 404 - /brand-demo/selectAllServlet错误解决原因-Servlet/JavaWeb/IDEA
检查xml文件的包名有无错误检查html文件的url有无写错,是否与Servlet的urlPatterns一致检查Servlet的urlpattern有没有写错(如写成name),检查doPost、doGet是否正常运行 注:IDEA新建Servlet时,默认的WebServlet注解中name需要改urlPatterns&…...
宁夏众智科技OA办公系统存在SQL注入漏洞
漏洞描述 宁夏众智科技OA办公系统存在SQL注入漏洞 漏洞复现 POC POST /Account/Login?ACTIndex&CLRHome HTTP/1.1 Host: Content-Length: 45 Cache-Control: max-age0 Origin: http://39.105.48.206 Content-Type: application/x-www-form-urlencoded Upgrade-Insecur…...
Spring邮件发送:配置与发送邮件详细步骤?
Spring邮件发送教程指南?怎么用Spring邮件发送服务? Spring框架提供了强大的邮件发送支持,使得开发者能够轻松地在应用程序中集成邮件发送功能。AokSend将详细介绍如何在Spring应用中配置和发送邮件,帮助开发者快速掌握这一关键技…...
iPhone/iPad技巧:如何解锁锁定的 iPhone 或 iPad
“在我更新 iPhone 上的软件后,最近我遇到了iPhone 被锁定到所有者的消息,该如何解决?” 根据我们的研究,许多用户在 iOS 18 更新或恢复出厂设置后都会遇到同样的问题。只要出现问题,您就无法使用 iPhone 或 第 1 部分…...
无源码实现免登录功能
因项目要求需要对一个没有源代码的老旧系统实现免登录功能,系统采用前后端分离的方式部署,登录时前端调用后台的认证接口,认证接口返回token信息,然后将token以json的方式存储到cookie中,格式如下: 这里有…...
大数据毕业设计选题推荐-民族服饰数据分析系统-Python数据可视化-Hive-Hadoop-Spark
✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...
疾风大模型气象,基于气象数据打造可视化平台
引言 随着气象数据的广泛应用,越来越多的行业依赖天气预报与气候分析来做出决策。从农业、航空、能源到物流,气象信息无时不刻影响着各行各业的运作。然而,气象数据本身复杂且多样,如何将这些数据转化为直观、易于理解的图形和信…...
PHP安装后Apache无法运行的问题
问题 按照网上教程php安装点击跳转教程,然后修改Apache的httpd.conf文件,本来可以运行的Apache,无法运行了 然后在"C:\httpd-2.4.62-240904-win64-VS17\Apache24\logs\error.log"(就是我下载Apache的目录下的logs中&am…...
[论文精读]Multi-Channel Graph Neural Network for Entity Alignment
论文网址:Multi-Channel Graph Neural Network for Entity Alignment (aclanthology.org) 论文代码:https:// github.com/thunlp/MuGNN 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&a…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
