【学习笔记】手写 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…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...