NIO,编写Tomcat
Java NIO
Java NIO主要有三大核心部分:
- Buffer(缓冲区)
- Channel(通道)
- Selector(选择器)
Buffer
缓冲区其实是一个容器对象,也就是一个数组。在NIO中所有数据面向缓存区处理的(传统I/O处理数据都是面向流) ,在读取数据时,他是直接读取的缓冲区的数据。在写入数据的时候,他也是直接写入缓冲区。在缓冲区中有三个重要的属性:
- position:指定下一个将要被写入和读取的元素索引,他的值由get()/put()方法自动更新,在创建Buffer对象时,position初始化为0
- 指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)
- capacity:指定了缓冲区的最大数据容量
Channel
我们一般讲他翻译为 ”通道“ ,他和I/O中的Stream流差不多。但是Stream流是单向的,例如:InputStream和OutputStream。
Selector
传统的Client/Server模式基于TPR(Thread per Request),服务器会为每个客户端建立一个线程,由该线程负责客户端的请求。这种模式就会导致线程数量剧增,给服务器造成巨大的压力。
NIO中非阻塞I/O采用了Reactor工作模式,I/O调用不会被阻塞。NIO中实现非阻塞I/O的核心对象就是Selector,Selector是注册各种I/O事件的地方。而且当哪些事件发生时,就是Selector告诉我们所发生的事件,如下图:
当有读写等任何注册事件发生时,可以从Selector中获取相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体SelectableChannel
手写一个简单Tomcat
我们知道Tomcat是一个居于J2EE规范的web容器,主要入口是web.xml文件。web.xml文件中主要配置Servlet、Filter、Listener。我这实例主要重写一个Servlet,项目架构图如下:
Request与Response
因为是基于HTTP协议,主要构成如:
封装request代码如下:
-
import java.io.IOException;
-
import java.nio.ByteBuffer;
-
import java.nio.channels.SelectionKey;
-
import java.nio.channels.SocketChannel;
-
-
/**
-
* request
-
* @author feng
-
*/
-
public class NIORequest {
-
-
private String method;
-
-
private String url;
-
-
public NIORequest (SelectionKey selectionKey) throws IOException {
-
// 获取selectionKey实例中的通道
-
SocketChannel channel = (SocketChannel) selectionKey.channel();
-
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
-
-
// 从通道中读取数据到buffer
-
int length = 0;
-
length = channel.read(byteBuffer); // 从通道中读取数据到ByteBuffer容器中
-
if (length < 0){
-
selectionKey.cancel(); // 取消selectionKey实例
-
}else {
-
byteBuffer.flip();
-
//将byteBuffer转为String
-
String httpRequest = new String(byteBuffer.array()).trim();
-
byteBuffer.clear();
-
// 获取请求头
-
String requestHeaders = httpRequest.split("\n")[0];
-
// 获取请求路径url
-
url = requestHeaders.split("\\s")[1].split("\\?")[0];
-
// 获取请求方式
-
method = requestHeaders.split("\\s")[0];
-
}
-
}
-
-
public String getMethod() {
-
return method;
-
}
-
-
public String getUrl() {
-
return url;
-
}
-
-
}
response代码如下:
-
import java.io.IOException;
-
import java.nio.ByteBuffer;
-
import java.nio.channels.SelectionKey;
-
import java.nio.channels.SocketChannel;
-
-
/**
-
* response
-
* @author feng
-
*/
-
public class NIOResponse {
-
-
private SelectionKey selectionKey;
-
-
public NIOResponse(SelectionKey selectionKey) {
-
this.selectionKey = selectionKey;
-
}
-
-
public void write(String out) throws IOException {
-
// 输出也要遵循HTTP
-
// 状态码为200
-
StringBuilder sb = new StringBuilder();
-
sb.append("HTTP/1.1 200 OK\n")
-
.append("Content-Type: text/html;\n")
-
.append("\r\n")
-
.append(out);
-
// 设置编码UTF-8
-
ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes("UTF-8"));
-
// 从selectionKey实例中获取通道
-
SocketChannel channel = (SocketChannel) selectionKey.channel();
-
// 向通道中写入数据
-
int length = channel.write(byteBuffer);
-
selectionKey.cancel();
-
channel.close();
-
-
}
-
}
Servlet代码:
-
**
-
* Servlet
-
*feng
-
*/
-
public abstract class NIOServlet {
-
-
// 简单支持POST
-
public void service(NIORequest request ,NIOResponse response) throws Exception{
-
if ("GET".equalsIgnoreCase(request.getMethod())){
-
doGet(request,response);
-
}else {
-
doPost(request,response);
-
}
-
}
-
-
public abstract void doGet(NIORequest request ,NIOResponse response) throws Exception;
-
-
public abstract void doPost(NIORequest request ,NIOResponse response) throws Exception;
-
-
}
TestServlet:自定义访问的servlet
-
public class TestServlet extends NIOServlet{
-
-
public void doGet(NIORequest request, NIOResponse response) throws Exception {
-
this.doPost(request,response);
-
}
-
-
-
public void doPost(NIORequest request, NIOResponse response) throws Exception {
-
response.write("This is Test Servlet!");
-
}
-
}
web.xml配置 这里我改用web.properties代替读取配置:
-
servlet.one.url=/testServlet
-
servlet.one.className=com.company.TestServlet
Tomcat服务端代码:
-
package com.company;
-
-
import java.io.FileInputStream;
-
import java.io.IOException;
-
import java.net.InetSocketAddress;
-
import java.net.Socket;
-
import java.nio.channels.*;
-
import java.nio.channels.spi.SelectorProvider;
-
import java.util.*;
-
import java.util.concurrent.ConcurrentLinkedQueue;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
-
/**
-
* @author feng
-
*/
-
public class NIOTomcat {
-
-
// 端口
-
private int port;
-
-
private Selector selector;
-
-
//采用线程池处理数据
-
private ExecutorService executorService = Executors.newCachedThreadPool();
-
-
//储存servlet
-
private Map<String, NIOServlet> servletMap = new HashMap<>();
-
-
private Properties webXml = new Properties();
-
-
public NIOTomcat(int port) {
-
this.port = port;
-
}
-
-
/**
-
* 获取web-properties配置
-
*/
-
private void init() throws Exception {
-
try (FileInputStream fileInputStream = new
-
FileInputStream("web.properties")) {
-
webXml.load(fileInputStream);
-
for (Object web : webXml.keySet()) {
-
String key = web.toString();
-
if (key.endsWith(".url")) {
-
String servletName = key.replaceAll("\\.url$", "");
-
String url = webXml.getProperty(key);
-
String className = webXml.getProperty(servletName ".className");
-
// 单实例 多线程
-
NIOServlet obj = (NIOServlet) Class.forName(className).newInstance();
-
servletMap.put(url, obj);
-
}
-
}
-
}
-
-
-
}
-
-
/**
-
* 启动服务器
-
*/
-
private void start() throws Exception {
-
// 1. 加载配置文件,初始化servletMap
-
init();
-
// 2.启动selector
-
selector = Selector.open();
-
// 3.开启Channel通道
-
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
-
// 4.配置通道非阻塞
-
serverSocketChannel.configureBlocking(false);
-
// 5.绑定地址端口
-
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", port));
-
serverSocketChannel.accept();
-
// 6.将通道注册给监听器
-
SelectionKey register = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
-
-
System.out.println("Tomcat 已启动,监听的端口是:" this.port);
-
-
// 使用队列存储request和response
-
ConcurrentLinkedQueue<NIORequest> requestList = new ConcurrentLinkedQueue<>();
-
ConcurrentLinkedQueue<NIOResponse> responseList = new ConcurrentLinkedQueue<>();
-
-
// 7. 轮询查询就绪操作
-
while (true) {
-
// 选择一些I/O操作已经准备好的管道。每个管道对应着一个key。这个方法 是一个阻塞的选择操作。当至少有一个通道被选择时才返回。当这个方法被执行时,当前线程是允许被中断的。
-
int n = selector.select();
-
if (n > 0) {
-
// 获取监听器的所有事件并进行业务处理
-
Set<SelectionKey> selectionKeys = selector.selectedKeys();
-
Iterator<SelectionKey> iterator = selectionKeys.iterator();
-
while (iterator.hasNext()) {
-
SelectionKey selectionKey = iterator.next();
-
// 连接事件
-
if (selectionKey.isAcceptable()) {
-
//开启读取监听
-
doRead(selectionKey);
-
} else if (selectionKey.isValid() && selectionKey.isReadable()) { //可读事件
-
requestList.add(getRequest(selectionKey));
-
//切换可写的状态
-
selectionKey.interestOps(SelectionKey.OP_WRITE);
-
} else if (selectionKey.isValid() && selectionKey.isWritable()) { //可写事件
-
responseList.add(getResponse(selectionKey));
-
//切换可读的状态
-
selectionKey.interestOps(SelectionKey.OP_READ);
-
}
-
-
//请求和响应都准备好的时候进行处理
-
if (!requestList.isEmpty() && !responseList.isEmpty()) {
-
dopath(requestList.poll(), responseList.poll());
-
}
-
//删除事件,免得后面会重复处理该事件
-
iterator.remove();
-
}
-
}
-
}
-
-
}
-
-
/**
-
* 执行请求
-
*/
-
private void dopath(NIORequest request, NIOResponse response) throws Exception {
-
if (request == null) {
-
return;
-
}
-
if (response == null) {
-
return;
-
}
-
-
// 1.获取url
-
String url = request.getUrl();
-
-
-
executorService.execute(new Runnable() {
-
-
public void run() {
-
if (servletMap.containsKey(url)) {
-
// 6. servlet
-
try {
-
servletMap.get(url).service(request, response);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
} else {
-
try {
-
response.write("404 - Not Found");
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
});
-
-
}
-
-
-
/**
-
* 开启读取监听
-
*
-
* @param selectionKey
-
*/
-
private void doRead(SelectionKey selectionKey) {
-
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
-
SocketChannel socketChannel = null;
-
try {
-
socketChannel = channel.accept();
-
socketChannel.configureBlocking(false);
-
socketChannel.register(selector, SelectionKey.OP_READ);
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
/**
-
* 从通道中获取请求,返回自定义NIORequest
-
*
-
* @param selectionKey selectionKey实例
-
* @return NIORequest
-
*/
-
private NIORequest getRequest(SelectionKey selectionKey) throws IOException {
-
return new NIORequest(selectionKey);
-
}
-
-
/**
-
* 从通道中获取请求,返回自定义NIOResponse
-
*
-
* @param selectionKey selectionKey实例
-
* @return NIOResponse
-
*/
-
private NIOResponse getResponse(SelectionKey selectionKey) throws IOException {
-
return new NIOResponse(selectionKey);
-
}
-
-
-
public static void main(String[] args) throws Exception {
-
new NIOTomcat(8888).start();
-
}
-
-
-
}
启动Tomcat
访问接口:
一个由基于NIO手写的Tomcat就完工了。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhghib
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24