目录
一、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 详解-****博客
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、注解等。这些内容组合起来就是一个简单的框架