• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

NIO,编写Tomcat

武飞扬头像
sunny_feng2
帮助1

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代码如下:

  1.  
    import java.io.IOException;
  2.  
    import java.nio.ByteBuffer;
  3.  
    import java.nio.channels.SelectionKey;
  4.  
    import java.nio.channels.SocketChannel;
  5.  
     
  6.  
    /**
  7.  
    * request
  8.  
    * @author feng
  9.  
    */
  10.  
    public class NIORequest {
  11.  
     
  12.  
    private String method;
  13.  
     
  14.  
    private String url;
  15.  
     
  16.  
    public NIORequest (SelectionKey selectionKey) throws IOException {
  17.  
    // 获取selectionKey实例中的通道
  18.  
    SocketChannel channel = (SocketChannel) selectionKey.channel();
  19.  
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  20.  
     
  21.  
    // 从通道中读取数据到buffer
  22.  
    int length = 0;
  23.  
    length = channel.read(byteBuffer); // 从通道中读取数据到ByteBuffer容器中
  24.  
    if (length < 0){
  25.  
    selectionKey.cancel(); // 取消selectionKey实例
  26.  
    }else {
  27.  
    byteBuffer.flip();
  28.  
    //将byteBuffer转为String
  29.  
    String httpRequest = new String(byteBuffer.array()).trim();
  30.  
    byteBuffer.clear();
  31.  
    // 获取请求头
  32.  
    String requestHeaders = httpRequest.split("\n")[0];
  33.  
    // 获取请求路径url
  34.  
    url = requestHeaders.split("\\s")[1].split("\\?")[0];
  35.  
    // 获取请求方式
  36.  
    method = requestHeaders.split("\\s")[0];
  37.  
    }
  38.  
    }
  39.  
     
  40.  
    public String getMethod() {
  41.  
    return method;
  42.  
    }
  43.  
     
  44.  
    public String getUrl() {
  45.  
    return url;
  46.  
    }
  47.  
     
  48.  
    }
学新通

        response代码如下:

        

  1.  
    import java.io.IOException;
  2.  
    import java.nio.ByteBuffer;
  3.  
    import java.nio.channels.SelectionKey;
  4.  
    import java.nio.channels.SocketChannel;
  5.  
     
  6.  
    /**
  7.  
    * response
  8.  
    * @author feng
  9.  
    */
  10.  
    public class NIOResponse {
  11.  
     
  12.  
    private SelectionKey selectionKey;
  13.  
     
  14.  
    public NIOResponse(SelectionKey selectionKey) {
  15.  
    this.selectionKey = selectionKey;
  16.  
    }
  17.  
     
  18.  
    public void write(String out) throws IOException {
  19.  
    // 输出也要遵循HTTP
  20.  
    // 状态码为200
  21.  
    StringBuilder sb = new StringBuilder();
  22.  
    sb.append("HTTP/1.1 200 OK\n")
  23.  
    .append("Content-Type: text/html;\n")
  24.  
    .append("\r\n")
  25.  
    .append(out);
  26.  
    // 设置编码UTF-8
  27.  
    ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes("UTF-8"));
  28.  
    // 从selectionKey实例中获取通道
  29.  
    SocketChannel channel = (SocketChannel) selectionKey.channel();
  30.  
    // 向通道中写入数据
  31.  
    int length = channel.write(byteBuffer);
  32.  
    selectionKey.cancel();
  33.  
    channel.close();
  34.  
     
  35.  
    }
  36.  
    }
学新通

Servlet代码:

        

  1.  
    **
  2.  
    * Servlet
  3.  
    * @author feng
  4.  
    */
  5.  
    public abstract class NIOServlet {
  6.  
     
  7.  
    // 简单支持POST
  8.  
    public void service(NIORequest request ,NIOResponse response) throws Exception{
  9.  
    if ("GET".equalsIgnoreCase(request.getMethod())){
  10.  
    doGet(request,response);
  11.  
    }else {
  12.  
    doPost(request,response);
  13.  
    }
  14.  
    }
  15.  
     
  16.  
    public abstract void doGet(NIORequest request ,NIOResponse response) throws Exception;
  17.  
     
  18.  
    public abstract void doPost(NIORequest request ,NIOResponse response) throws Exception;
  19.  
     
  20.  
    }
学新通

        TestServlet:自定义访问的servlet

  1.  
    public class TestServlet extends NIOServlet{
  2.  
    @Override
  3.  
    public void doGet(NIORequest request, NIOResponse response) throws Exception {
  4.  
    this.doPost(request,response);
  5.  
    }
  6.  
     
  7.  
    @Override
  8.  
    public void doPost(NIORequest request, NIOResponse response) throws Exception {
  9.  
    response.write("This is Test Servlet!");
  10.  
    }
  11.  
    }

        web.xml配置 这里我改用web.properties代替读取配置:

  1.  
    servlet.one.url=/testServlet
  2.  
    servlet.one.className=com.company.TestServlet

         Tomcat服务端代码:

  1.  
    package com.company;
  2.  
     
  3.  
    import java.io.FileInputStream;
  4.  
    import java.io.IOException;
  5.  
    import java.net.InetSocketAddress;
  6.  
    import java.net.Socket;
  7.  
    import java.nio.channels.*;
  8.  
    import java.nio.channels.spi.SelectorProvider;
  9.  
    import java.util.*;
  10.  
    import java.util.concurrent.ConcurrentLinkedQueue;
  11.  
    import java.util.concurrent.ExecutorService;
  12.  
    import java.util.concurrent.Executors;
  13.  
     
  14.  
    /**
  15.  
    * @author feng
  16.  
    */
  17.  
    public class NIOTomcat {
  18.  
     
  19.  
    // 端口
  20.  
    private int port;
  21.  
     
  22.  
    private Selector selector;
  23.  
     
  24.  
    //采用线程池处理数据
  25.  
    private ExecutorService executorService = Executors.newCachedThreadPool();
  26.  
     
  27.  
    //储存servlet
  28.  
    private Map<String, NIOServlet> servletMap = new HashMap<>();
  29.  
     
  30.  
    private Properties webXml = new Properties();
  31.  
     
  32.  
    public NIOTomcat(int port) {
  33.  
    this.port = port;
  34.  
    }
  35.  
     
  36.  
    /**
  37.  
    * 获取web-properties配置
  38.  
    */
  39.  
    private void init() throws Exception {
  40.  
    try (FileInputStream fileInputStream = new
  41.  
    FileInputStream("web.properties")) {
  42.  
    webXml.load(fileInputStream);
  43.  
    for (Object web : webXml.keySet()) {
  44.  
    String key = web.toString();
  45.  
    if (key.endsWith(".url")) {
  46.  
    String servletName = key.replaceAll("\\.url$", "");
  47.  
    String url = webXml.getProperty(key);
  48.  
    String className = webXml.getProperty(servletName ".className");
  49.  
    // 单实例 多线程
  50.  
    NIOServlet obj = (NIOServlet) Class.forName(className).newInstance();
  51.  
    servletMap.put(url, obj);
  52.  
    }
  53.  
    }
  54.  
    }
  55.  
     
  56.  
     
  57.  
    }
  58.  
     
  59.  
    /**
  60.  
    * 启动服务器
  61.  
    */
  62.  
    private void start() throws Exception {
  63.  
    // 1. 加载配置文件,初始化servletMap
  64.  
    init();
  65.  
    // 2.启动selector
  66.  
    selector = Selector.open();
  67.  
    // 3.开启Channel通道
  68.  
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  69.  
    // 4.配置通道非阻塞
  70.  
    serverSocketChannel.configureBlocking(false);
  71.  
    // 5.绑定地址端口
  72.  
    serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", port));
  73.  
    serverSocketChannel.accept();
  74.  
    // 6.将通道注册给监听器
  75.  
    SelectionKey register = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  76.  
     
  77.  
    System.out.println("Tomcat 已启动,监听的端口是:" this.port);
  78.  
     
  79.  
    // 使用队列存储request和response
  80.  
    ConcurrentLinkedQueue<NIORequest> requestList = new ConcurrentLinkedQueue<>();
  81.  
    ConcurrentLinkedQueue<NIOResponse> responseList = new ConcurrentLinkedQueue<>();
  82.  
     
  83.  
    // 7. 轮询查询就绪操作
  84.  
    while (true) {
  85.  
    // 选择一些I/O操作已经准备好的管道。每个管道对应着一个key。这个方法 是一个阻塞的选择操作。当至少有一个通道被选择时才返回。当这个方法被执行时,当前线程是允许被中断的。
  86.  
    int n = selector.select();
  87.  
    if (n > 0) {
  88.  
    // 获取监听器的所有事件并进行业务处理
  89.  
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
  90.  
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
  91.  
    while (iterator.hasNext()) {
  92.  
    SelectionKey selectionKey = iterator.next();
  93.  
    // 连接事件
  94.  
    if (selectionKey.isAcceptable()) {
  95.  
    //开启读取监听
  96.  
    doRead(selectionKey);
  97.  
    } else if (selectionKey.isValid() && selectionKey.isReadable()) { //可读事件
  98.  
    requestList.add(getRequest(selectionKey));
  99.  
    //切换可写的状态
  100.  
    selectionKey.interestOps(SelectionKey.OP_WRITE);
  101.  
    } else if (selectionKey.isValid() && selectionKey.isWritable()) { //可写事件
  102.  
    responseList.add(getResponse(selectionKey));
  103.  
    //切换可读的状态
  104.  
    selectionKey.interestOps(SelectionKey.OP_READ);
  105.  
    }
  106.  
     
  107.  
    //请求和响应都准备好的时候进行处理
  108.  
    if (!requestList.isEmpty() && !responseList.isEmpty()) {
  109.  
    dopath(requestList.poll(), responseList.poll());
  110.  
    }
  111.  
    //删除事件,免得后面会重复处理该事件
  112.  
    iterator.remove();
  113.  
    }
  114.  
    }
  115.  
    }
  116.  
     
  117.  
    }
  118.  
     
  119.  
    /**
  120.  
    * 执行请求
  121.  
    */
  122.  
    private void dopath(NIORequest request, NIOResponse response) throws Exception {
  123.  
    if (request == null) {
  124.  
    return;
  125.  
    }
  126.  
    if (response == null) {
  127.  
    return;
  128.  
    }
  129.  
     
  130.  
    // 1.获取url
  131.  
    String url = request.getUrl();
  132.  
     
  133.  
     
  134.  
    executorService.execute(new Runnable() {
  135.  
    @Override
  136.  
    public void run() {
  137.  
    if (servletMap.containsKey(url)) {
  138.  
    // 6. servlet
  139.  
    try {
  140.  
    servletMap.get(url).service(request, response);
  141.  
    } catch (Exception e) {
  142.  
    e.printStackTrace();
  143.  
    }
  144.  
    } else {
  145.  
    try {
  146.  
    response.write("404 - Not Found");
  147.  
    } catch (IOException e) {
  148.  
    e.printStackTrace();
  149.  
    }
  150.  
    }
  151.  
    }
  152.  
    });
  153.  
     
  154.  
    }
  155.  
     
  156.  
     
  157.  
    /**
  158.  
    * 开启读取监听
  159.  
    *
  160.  
    * @param selectionKey
  161.  
    */
  162.  
    private void doRead(SelectionKey selectionKey) {
  163.  
    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
  164.  
    SocketChannel socketChannel = null;
  165.  
    try {
  166.  
    socketChannel = channel.accept();
  167.  
    socketChannel.configureBlocking(false);
  168.  
    socketChannel.register(selector, SelectionKey.OP_READ);
  169.  
    } catch (IOException e) {
  170.  
    e.printStackTrace();
  171.  
    }
  172.  
    }
  173.  
     
  174.  
    /**
  175.  
    * 从通道中获取请求,返回自定义NIORequest
  176.  
    *
  177.  
    * @param selectionKey selectionKey实例
  178.  
    * @return NIORequest
  179.  
    */
  180.  
    private NIORequest getRequest(SelectionKey selectionKey) throws IOException {
  181.  
    return new NIORequest(selectionKey);
  182.  
    }
  183.  
     
  184.  
    /**
  185.  
    * 从通道中获取请求,返回自定义NIOResponse
  186.  
    *
  187.  
    * @param selectionKey selectionKey实例
  188.  
    * @return NIOResponse
  189.  
    */
  190.  
    private NIOResponse getResponse(SelectionKey selectionKey) throws IOException {
  191.  
    return new NIOResponse(selectionKey);
  192.  
    }
  193.  
     
  194.  
     
  195.  
    public static void main(String[] args) throws Exception {
  196.  
    new NIOTomcat(8888).start();
  197.  
    }
  198.  
     
  199.  
     
  200.  
    }
学新通

启动Tomcat

        学新通

 访问接口:

        学新通

 一个由基于NIO手写的Tomcat就完工了。

     

        

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhghib
系列文章
更多 icon
同类精品
更多 icon
继续加载