详解Tomcat是如何实现异步Servlet的

详解Tomcat如何实现异步Servlet。

什么是异步Servlet

Servlet是JavaWeb应用程序中的一种Java类,用于处理接收到的HTTP请求并返回HTTP响应。在Web应用程序的架构中,Servlet在Web服务器(如Tomcat)和Web客户端之间充当中间件的角色。在同步编程模型中,Servlet在完成处理请求后,直接返回结果给Web客户端。这种模型下,客户端在收到响应之前会被一直阻塞。

异步Servlet允许开发者处理请求时不阻塞客户端的请求,而是在请求处理的阶段之间释放HTTP连接,允许服务器继续处理其他请求。异步功能增强了服务器的性能,因为工作线程不会一直处于占用状态。

Tomcat实现异步Servlet的原理

Tomcat通过Servlet 3.0规范中引入的异步Servlet API来实现异步请求处理。异步API是在Servlet 3.0规范中引入的,它允许异步处理的开发人员能够控制HTTP连接什么时候释放,在什么时候响应客户端。

Tomcat的异步API利用了Java NIO(New I/O)库,这意味着终止任务时无需创建任何新的线程。与直接使用线程不同,NIO库允许单个线程在等待基于事件的输入和输出时执行其他任务。Tomcat使用NIO库来实现异步请求处理。当异步请求驱动程序调用ServletResponse的许多的完成处理程序(complete()),Tomcat才会从其EPoll管理器中删除某个管道,从而减少对服务器性能的影响。

异步请求的处理顺序如下:

  1. Web容器接收到HTTP请求,创建ServletRequest的实例和ServletResponse的实例

  2. Servlet线程开始异步处理请求

  3. Servlet在异步线程池中分配一个线程,将处理请求的工作交给此线程

  4. Servlet线程完成所有必需的初始化或配置,并调用ServletRequest.startAsync()方法启动异步处理

  5. 应用程序将ServletRequest.submit() 方法和一个AsyncListener实例或AsyncContext实例一起调用,从而使容器知道异步操作已经完成

  6. Servlet线程退出,并将HTTP连接返回到Tomcat

  7. Web容器将当前请求上下文保存到内存池中,将HTTP连接返回给Tomcat以便处理其他请求

  8. 当异步操作完成时,应用程序可以调用AsyncListener的onComplete() 方法或AsyncContext的complete() 方法来通知容器异步操作已经完成,并且在ServletResponse完成响应之前不要释放HTTP连接

  9. Web容器的管理员将ServletRequest上下文从内存池中删除,使得与此上下文关联的所有对象可以被垃圾回收

示例1:异步响应

我们可以使用如下代码启用异步响应:

@WebServlet(urlPatterns={"/asyncServlet"})  
public class asyncServlet extends HttpServlet {  
    private static final long serialVersionUID = 1L;  

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try{  
            response.setContentType("text/html");  
            response.setCharacterEncoding("utf-8");  
            AsyncContext context = request.startAsync();  
            ServletOutputStream outStream = response.getOutputStream();  
            outStream.println("<html><body><h3>异步Servlet示例1:</h3>");  
            outStream.println("<h3>等待十秒钟</h3>");  
            Executor executor = Executors.newSingleThreadExecutor();  
            executor.execute(() -> {    
                try{    
                    //模拟长时间运行的代码    
                    Thread.sleep(10000);    
                    //异步操作已经完成    
                    outStream.println("<h3>异步请求已经完成:" + new Date() + "</h3>");  
                    //使用complete方法通知容器异步请求已经完成    
                    context.complete();    
                }catch(InterruptedException e){    
                    e.printStackTrace();    
                }    
            });      
            outStream.println("</body></html>");  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
    }    
}  

在这个例子中,Servlet先输出一些HTML并启用异步模式。接下来,异步操作由Executors类的单线程线程池执行,此操作将模拟需要等待10秒钟的长时间运行过程。当异步操作完成时,将使用AsyncContext的completed()方法通知容器,而不是等待完整的10秒钟才返回响应。请注意:字符串“异步请求已经完成”(Async request complete)的显示时间与输出HTML页面的显示时间不同。

示例2:异步读写文件

下面的示例演示了如何在异步Servlet中读写文件。在这个例子中,我们使用NIO来进行异步文件读取和写入操作。

@WebServlet(urlPatterns={"/asyncServlet2"})  
public class AsyncServlet2 extends HttpServlet {  
    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {  
        final AsyncContext context = request.startAsync(request, response);  
        final ServletOutputStream outStream = response.getOutputStream();  
        outStream.println("<html><head>");  
        outStream.println("<script src='http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js'></script>");  
        outStream.println("<script>");  
        outStream.println("function myFunction() {");  

        outStream.println("$.ajax({");  
        outStream.println("url: 'getFile',");  
        outStream.println("type: 'POST',");  
        outStream.println("success: function(data) {");  
        outStream.println("var fileName=JSON.parse(data).fileName;");  
        outStream.println("var fileSize=JSON.parse(data).fileSize;");  
        outStream.println("$('#fileName').text(fileName);");  
        outStream.println("$('#fileSize').text(fileSize);");  
        outStream.println("if(fileSize!=0){");  

        outStream.println("var download=$('<a>').html('Download File').attr('href','getFileContent');");  
        outStream.println("$('#fileList').append(download);");  
        outStream.println("}");  
        outStream.println("},");  
        outStream.println("error: function(data) { alert('failed'); }");  
        outStream.println("});");  
        outStream.println("}");  
        outStream.println("</script>");  
        outStream.println("</head>");  
        outStream.println("<body onload=\"myFunction()\">");  
        outStream.println("<h3>异步读写文件</h3>");  
        outStream.println("<div>");  
        outStream.println("<p>File name: <label id=\"fileName\"></label></p>");  
        outStream.println("<p>File size: <label id=\"fileSize\"></label></p>");  
        outStream.println("<p id=\"fileList\"></p>");  
        outStream.println("</div></body></html>");  

        //将工作线程移到独立线程  
        context.start(new Runnable() {  
            public void run() {  
                try{    
                    RandomAccessFile file = new RandomAccessFile("data.txt", "r");    
                    FileChannel fileChannel = file.getChannel();    
                    ByteBuffer buffer = ByteBuffer.allocate(1000);    
                    //开始异步读取  
                    fileChannel.read(buffer, fileChannel.position(), buffer,    
                                new CompletionHandler<Integer, ByteBuffer>() {    
                                    public void completed(Integer result, ByteBuffer attachment) {    
                                        try{    
                                            //准备写入:反转缓冲区    
                                            attachment.flip();    
                                            FileChannel fileChannel2 = new FileOutputStream("data.bak").getChannel();    
                                            //开始异步写入    
                                            fileChannel2.write(attachment, fileChannel2.size(), attachment,    
                                                new CompletionHandler<Integer, ByteBuffer>() {    
                                                    public void completed(Integer result, ByteBuffer attachment) {
                                                        try{    
                                                            //继续读取数据    
                                                            if(fileChannel2.size() > fileChannel2.position()){  
                                                                ByteBuffer buffer = ByteBuffer.allocate(1000);    
                                                                fileChannel.read(buffer, fileChannel2.position(), buffer, this);    
                                                            }else{  
                                                                //使用complete方法通知容器异步请求已经完成    
                                                                context.complete();  
                                                            }    
                                                        }catch(Exception e){    
                                                            e.printStackTrace();    
                                                        }    
                                                    }    
                                                    public void failed(Throwable exc, ByteBuffer attachment) {    
                                                        exc.printStackTrace();    
                                                    }    
                                                });    
                                        }catch(Exception e){    
                                            e.printStackTrace();    
                                        }    
                                    }    
                                    public void failed(Throwable exc, ByteBuffer attachment) {  
                                        exc.printStackTrace();  
                                    }  
                            });    
                }catch(Exception e){    
                    e.printStackTrace();    
                }    
            }  
        });  
    }  
}  

在这个Servlet中,我们使用ajax技术从服务器获取文件的元数据。在客户端JavaScript代码生成页面时,使用了一个函数,这个函数使用$.ajax()方法调用异步Servlet HTTP文件接口。在异步模式开头,工作线程切换到另一个线程并调用文件处理代码。注意:在这个示例中,异步文件处理包括使用Java NIO异步读取数据和将此数据异步写入到另一个文件。

这就是Tomcat如何实现异步Servlet的完整攻略,希望能对你有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Tomcat是如何实现异步Servlet的 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • springboot整合持久层的方法实现

    Spring Boot是一个非常流行的Java Web框架,它提供了很多方便的功能来简化应用程序的开发。其中,整合持久层是Spring Boot应用程序中的一个重要部分。以下是Spring Boot整合持久层的方法实现的详细攻略: 选择持久层框架 在Spring Boot中,我们可以选择使用多种持久层框架,如Hibernate、MyBatis、Spring …

    Java 2023年5月15日
    00
  • JS注释所产生的bug 即使注释也会执行

    JS注释所产生的bug是指在一些情况下,即使代码中存在注释,这些注释也会被执行而导致程序出现问题。 该问题主要是因为在一些JS引擎中,被注释的代码可能在编译阶段和解析阶段都会被执行,因此如果注释中包含了有效的代码,则这些代码会被直接执行。这就引起了一定的安全隐患,也可能导致代码出现逻辑错误。 下面通过两个示例来说明该问题: 示例一: function tes…

    Java 2023年6月15日
    00
  • 什么是线程池?

    以下是关于线程池的完整使用攻略: 什么是线程池? 线程池是一种用于管理和复用线程的机制,它可以在程序启动时创建一定数量的线程,并将这些线程保存在一个池中,当需要执行任务时,从池中取出一个线程执行任务,任务执行完成后,线程不会被销毁而是返回到池中等待下一次任务的执行。线程池可以有效地减线程的创建和销毁次数,从而提高程序的性能和效率。 线程池的优点 线程池的优点…

    Java 2023年5月12日
    00
  • 提升Ruby on Rails性能的几个解决方案

    当网站的流量愈发庞大之后,Ruby on Rails的性能问题可能会变得突出。在这篇文章中,我们将分享一些提升Ruby on Rails性能的解决方案。以下是一些示例: 1. 数据库优化 首先,可以考虑数据库优化。你可以执行以下一些操作: 添加索引:你可以从应用程序中查找慢速查询,并使用适当的索引进行优化。例如,在查询中使用一个where子句时,可以在相应W…

    Java 2023年6月2日
    00
  • Java实现快速幂算法详解

    Java实现快速幂算法详解 快速幂算法(Power Mod)可用来求解形如$a^b\%c$的表达式,其中$a$、$b$和$c$均为正整数。快速幂算法可通过将$b$的二进制分解,以分治的方式加速幂数的计算。 算法流程 将幂数$b$转化为二进制数 遍历二进制数中每一位,从高位到低位,若该位上的二进制数字为1,则将当前幂数乘上底数$a$,否则幂数不变。 将所得的幂…

    Java 2023年5月19日
    00
  • Spring Boot(二)之web综合开发

    Spring Boot(二)之web综合开发 在本篇文章中,我们将介绍如何使用Spring Boot进行Web开发的综合性攻略。具体来说,将包含以下内容: Spring Boot中MVC的概念以及使用方法; 整合Thymeleaf和Bootstrap实现前端页面渲染; 利用Spring Boot提供的数据持久化机制与数据库进行交互; Spring Boot中…

    Java 2023年6月15日
    00
  • 浅谈java日志格式化

    浅谈Java日志格式化 什么是日志格式化 在进行Java应用开发的过程中,日志系统是必不可少的一个组件。日志格式化就是在记录应用程序运行中产生的日志信息时,对不同的信息类型进行分类、分级,并为每一条日志信息提供一个易于读取和理解的格式,以方便后续的调试、运维和分析工作。 日志格式化的重要性 在一个应用程序中,日志系统是一个非常重要的组件。通过日志系统,可以帮…

    Java 2023年5月26日
    00
  • springboot+dynamicDataSource动态添加切换数据源方式

    使用 Spring Boot,可以动态添加切换数据源,需要用到Spring JDBC模块中的 AbstractRoutingDataSource 类和 DynamicDataSourceHolder 维护一个存储当前使用的数据源 key 的 ThreadLocal 对象。步骤如下: 导入依赖 首先,在 pom.xml 中导入 Spring Boot 和 Sp…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部