在手写tomcat(三)中,我实现了基于BIO模型的多线程tomcat,而这次我实现了Nio的多线程模型, 2020年4月17日,我将该文章进行了修改,原先写的是什么**玩意儿.
以下是基于NIO模型监听请求以及开启线程处理收到的请求。
首先是服务器,负责接收请求和断开连接等。
HttpServer.java:
package com.tomcat.nio;
import com.tomcat.baseservlet.AbstractServlet;
import java.io.IOException;
import java.net.InetSocketAddress;
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.*;
/**
* NIO版本的tomcat
* 监听请求,调用request和response对请求作出反应
* @author 申劭明
* @date 2020/4/18 17:21
* @version 5.1
*/
public class HttpServer {
/**
* 监听端口
*/
private static int port = 8080;
/**
* Key值为Servlet的别名(uri),value为该Servlet对象
* default权限
*/
private static HashMap<String, AbstractServlet> map;
/**
* 监听通道
*/
private ServerSocketChannel serverSocketChannel;
/**
* NIO负责轮询的Selector
*/
private Selector selector;
/**
* @Description : nio监听数据请求
* @author : 申劭明
* @date : 2019/9/17 10:29
*/
public void acceptWait() {
try {
serverSocketChannel = ServerSocketChannel.open();
// 设置ServerSocketChannel为非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(port));
//selector获取不同操作系统下不同的TCP连接动态
selector = Selector.open();
//给当前的serverSocketChannel注册选择器,根据条件查询符合情况的TCP连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
try {
if (selector.select(1000) == 0) {
// 阻塞式获取请,有几个被selector发现了网络请求,这个方法的返回值就是多少
// 没有网络请求
continue;
}
// 获取当前时间点的所有事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历所有有事件发生的通道
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 判断当前通道是否已经做好socket连接的准备
if (key.isAcceptable()) {
//拿到新的对象
SocketChannel channel = serverSocketChannel.accept();
if (channel != null) {
// 注册连接对象,进行关注,no-Blocking
channel.configureBlocking(false);
// 将该 socketChannel 通道注册到selector中
// 注意,上面的注册是ServerSocketChannel
channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
} else if (key.isReadable()) {
//如果当前通道是可读的
SocketChannel socketChannel = (SocketChannel) key.channel();
//处理过程中,先取消selector对应连接的注册,避免重复
key.cancel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
socketChannel.read(buffer);
// 处理消息
RequestHandler.handler(socketChannel, buffer);
// 关闭连接
socketChannel.close();
}
iterator.remove();
}
// 检查过程就绪,清除之前的调用效果
selector.selectNow();
} catch (IOException e) {
// 避免因为某一个请求异常而导致程序终止
e.printStackTrace();
}
}
}
}
接下来是对于请求的处理,时间关系我就不再像之前一样,将请求分发给Servlet了。
RequestHandler.java:
package com.tomcat.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @author 申劭明
* @date 2020/4/17 15:12
*/
public class RequestHandler {
/**
* @Description : nio中的消息处理
*
* @param socketChannel socket通道对象,相当于一次socket连接
* @param buffer 读取到的请求内容
* @Return : void
* @Author : 申劭明
* @Date : 2020/4/17 15:48
*/
static void handler(SocketChannel socketChannel, ByteBuffer buffer) throws IOException {
// 时间关系就不接之前的Servlet处理逻辑啦
System.out.println("Receive message: " + new String(buffer.array()));
String resultData = "HelloWorld";
socketChannel.write(ByteBuffer.wrap(resultData.getBytes()));
}
}