基于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
 * @author 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{
    @Override
    public void doGet(NIORequest request, NIOResponse response) throws Exception {
        this.doPost(request,response);
    }

    @Override
    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() {
            @Override
            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就完工了。

     

        

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值