Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/358.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Servlet-3异步上下文,如何进行异步写入? 问题描述_Java_Comet_Servlet 3.0 - Fatal编程技术网

Java Servlet-3异步上下文,如何进行异步写入? 问题描述

Java Servlet-3异步上下文,如何进行异步写入? 问题描述,java,comet,servlet-3.0,Java,Comet,Servlet 3.0,Servlet-3.0API允许分离请求/响应上下文,并在以后对其进行应答 但是,如果我尝试编写大量数据,例如: AsyncContext ac = getWaitingContext() ; ServletOutputStream out = ac.getResponse().getOutputStream(); out.print(some_big_data); out.flush() 对于Tomcat7和Jetty8,它实际上可能会阻塞—并且在一些简单的测试用例中也会阻塞。教程建议创建一

Servlet-3.0API允许分离请求/响应上下文,并在以后对其进行应答

但是,如果我尝试编写大量数据,例如:

AsyncContext ac = getWaitingContext() ;
ServletOutputStream out = ac.getResponse().getOutputStream();
out.print(some_big_data);
out.flush()
对于Tomcat7和Jetty8,它实际上可能会阻塞—并且在一些简单的测试用例中也会阻塞。教程建议创建一个线程池,它将 处理这样的设置-开关通常与传统的10K架构相反

但是如果我有10000个打开的连接和一个线程池,比如说10个线程, 对于1%的低速连接或刚刚被阻塞的客户端来说,这已经足够了 连接以阻止线程池并完全阻止comet响应或 显著降低速度

预期的做法是获得“写就绪”通知或I/O完成通知 然后继续推送数据

如何使用Servlet-3.0 API实现这一点,即如何获得:

  • I/O操作的异步完成通知
  • 使用写就绪通知获取非阻塞I/O
如果Servlet-3.0 API不支持这一点,那么是否有任何Web服务器特定的API(如Jetty Continuation或Tomcat CometEvent)允许真正异步地处理此类事件,而无需使用线程池假装异步I/O

有人知道吗

如果这是不可能的,你能参考文件确认吗

示例代码中的问题演示 我在下面附加了模拟事件流的代码

注:

  • 它使用
    ServletOutputStream
    抛出
    IOException
    来检测断开连接的客户端
  • 它发送
    保持活动状态
    消息以确保客户端仍然存在
  • 我创建了一个线程池来“模拟”异步操作
在这样的示例中,我显式定义了大小为1的线程池,以显示问题:

  • 启动应用程序
  • 从两个终端运行
    curlhttp://localhost:8080/path/to/app
    (两次)
  • 现在用
    curd-d m=消息发送数据http://localhost:8080/path/to/app
  • 两个客户都收到了数据
  • 现在暂停其中一个客户端(Ctrl+Z)并再次发送消息
    curd-d m=messagehttp://localhost:8080/path/to/app
  • 请注意,另一个未挂起的客户端没有收到任何消息,或者在消息传输后,由于另一个线程被阻塞而停止接收保持活动的请求
我想在不使用线程池的情况下解决这样一个问题,因为有1000-5000个线程是开放的 连接我可以很快耗尽线程池

下面是示例代码


import java.io.IOException;
导入java.util.HashSet;
导入java.util.Iterator;
导入java.util.concurrent.ThreadPoolExecutor;
导入java.util.concurrent.TimeUnit;
导入java.util.concurrent.LinkedBlockingQueue;
导入javax.servlet.AsyncContext;
导入javax.servlet.ServletConfig;
导入javax.servlet.ServletException;
导入javax.servlet.annotation.WebServlet;
导入javax.servlet.http.HttpServlet;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpServletResponse;
导入javax.servlet.ServletOutputStream;
@WebServlet(urlPatterns=“”,asyncSupported=true)
公共类HugeStreamWithThreads扩展了HttpServlet{
私有长id=0;
私有字符串消息=”;
专用最终线程池执行器池=
新的ThreadPoolExecutor(1,1,50000L,时间单位为毫秒,新的LinkedBlockingQueue());
//出于演示目的,它显然很小
私有最终线程计时器=新线程(new Runnable(){
公开募捐
{
试一试{
while(true){
睡眠(1000);
sendKeepAlive();
}
}
捕捉(中断异常e){
//出口
}
}
});
类RunJob实现Runnable{
volatile long lastUpdate=System.nanoTime();
长id=0;
异步上下文ac;
运行作业(异步上下文ac)
{
this.ac=ac;
}
公共void keepAlive()
{
如果(System.nanoTime()-lastUpdate>1000000000L)
提交(本);
}
字符串格式消息(字符串消息)
{
StringBuilder sb=新的StringBuilder();
某人附加(“id”);
某人(身份证);

对于(int i=0;i我发现
Servlet 3.0
异步
API很难正确实现,而且有用的文档很少。经过大量的尝试和错误,尝试了许多不同的方法,我能够找到一个我非常满意的健壮解决方案。当我查看我的代码并将其与您的代码进行比较时,我注意到一个可能帮助您解决特定问题的主要区别。我使用
ServletResponse
来写入数据,而不是
ServletOutputStream

这里,我的go to Asynchronous Servlet类稍微适应了您的
一些大数据
情况:

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

@javax.servlet.annotation.WebServlet(urlPatterns = { "/async" }, asyncSupported = true, initParams = { @WebInitParam(name = "threadpoolsize", value = "100") })
public class AsyncServlet extends HttpServlet {

  private static final Logger logger = Logger.getLogger(AsyncServlet.class);

  public static final int CALLBACK_TIMEOUT = 10000; // ms

  /** executor service */
  private ExecutorService exec;

  @Override
  public void init(ServletConfig config) throws ServletException {

    super.init(config);
    int size = Integer.parseInt(getInitParameter("threadpoolsize"));
    exec = Executors.newFixedThreadPool(size);
  }

  @Override
  public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    final AsyncContext ctx = req.startAsync();
    final HttpSession session = req.getSession();

    // set the timeout
    ctx.setTimeout(CALLBACK_TIMEOUT);

    // attach listener to respond to lifecycle events of this AsyncContext
    ctx.addListener(new AsyncListener() {

      @Override
      public void onComplete(AsyncEvent event) throws IOException {

        logger.info("onComplete called");
      }

      @Override
      public void onTimeout(AsyncEvent event) throws IOException {

        logger.info("onTimeout called");
      }

      @Override
      public void onError(AsyncEvent event) throws IOException {

        logger.info("onError called: " + event.toString());
      }

      @Override
      public void onStartAsync(AsyncEvent event) throws IOException {

        logger.info("onStartAsync called");
      }
    });

    enqueLongRunningTask(ctx, session);
  }

  /**
   * if something goes wrong in the task, it simply causes timeout condition that causes the async context listener to be invoked (after the fact)
   * <p/>
   * if the {@link AsyncContext#getResponse()} is null, that means this context has already timed out (and context listener has been invoked).
   */
  private void enqueLongRunningTask(final AsyncContext ctx, final HttpSession session) {

    exec.execute(new Runnable() {

      @Override
      public void run() {

        String some_big_data = getSomeBigData();

        try {

          ServletResponse response = ctx.getResponse();
          if (response != null) {
            response.getWriter().write(some_big_data);
            ctx.complete();
          } else {
            throw new IllegalStateException(); // this is caught below
          }
        } catch (IllegalStateException ex) {
          logger.error("Request object from context is null! (nothing to worry about.)"); // just means the context was already timeout, timeout listener already called.
        } catch (Exception e) {
          logger.error("ERROR IN AsyncServlet", e);
        }
      }
    });
  }

  /** destroy the executor */
  @Override
  public void destroy() {

    exec.shutdown();
  }
}
import java.io.IOException;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入javax.servlet.AsyncContext;
导入javax.servlet.AsyncEvent;
导入javax.servlet.AsyncListener;
导入javax.servlet.ServletConfig;
导入javax.servlet.ServletException;
导入javax.servlet.ServletResponse;
导入javax.servlet.annotation.WebInitParam;
导入javax.servlet.http.HttpServlet;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpServletResponse;
导入javax.servlet.http.HttpSession;
导入org.apache.log4j.Logger;
@javax.servlet.annotation.WebServlet(urlPatterns={”/async“},asyncSupported=true,initParams={@WebInitParam(name=“threadpoolsize”,value=“100”)})
公共类AsyncServlet扩展了HttpServlet{
P
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

@javax.servlet.annotation.WebServlet(urlPatterns = { "/async" }, asyncSupported = true, initParams = { @WebInitParam(name = "threadpoolsize", value = "100") })
public class AsyncServlet extends HttpServlet {

  private static final Logger logger = Logger.getLogger(AsyncServlet.class);

  public static final int CALLBACK_TIMEOUT = 10000; // ms

  /** executor service */
  private ExecutorService exec;

  @Override
  public void init(ServletConfig config) throws ServletException {

    super.init(config);
    int size = Integer.parseInt(getInitParameter("threadpoolsize"));
    exec = Executors.newFixedThreadPool(size);
  }

  @Override
  public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

    final AsyncContext ctx = req.startAsync();
    final HttpSession session = req.getSession();

    // set the timeout
    ctx.setTimeout(CALLBACK_TIMEOUT);

    // attach listener to respond to lifecycle events of this AsyncContext
    ctx.addListener(new AsyncListener() {

      @Override
      public void onComplete(AsyncEvent event) throws IOException {

        logger.info("onComplete called");
      }

      @Override
      public void onTimeout(AsyncEvent event) throws IOException {

        logger.info("onTimeout called");
      }

      @Override
      public void onError(AsyncEvent event) throws IOException {

        logger.info("onError called: " + event.toString());
      }

      @Override
      public void onStartAsync(AsyncEvent event) throws IOException {

        logger.info("onStartAsync called");
      }
    });

    enqueLongRunningTask(ctx, session);
  }

  /**
   * if something goes wrong in the task, it simply causes timeout condition that causes the async context listener to be invoked (after the fact)
   * <p/>
   * if the {@link AsyncContext#getResponse()} is null, that means this context has already timed out (and context listener has been invoked).
   */
  private void enqueLongRunningTask(final AsyncContext ctx, final HttpSession session) {

    exec.execute(new Runnable() {

      @Override
      public void run() {

        String some_big_data = getSomeBigData();

        try {

          ServletResponse response = ctx.getResponse();
          if (response != null) {
            response.getWriter().write(some_big_data);
            ctx.complete();
          } else {
            throw new IllegalStateException(); // this is caught below
          }
        } catch (IllegalStateException ex) {
          logger.error("Request object from context is null! (nothing to worry about.)"); // just means the context was already timeout, timeout listener already called.
        } catch (Exception e) {
          logger.error("ERROR IN AsyncServlet", e);
        }
      }
    });
  }

  /** destroy the executor */
  @Override
  public void destroy() {

    exec.shutdown();
  }
}
catch (InterruptedException x)
    throw (IOException)new InterruptedIOException().initCause(x);
public void run()
{
    String message = null;
    synchronized(HugeStreamWithThreads.this) {
        if(this.id != HugeStreamWithThreads.this.id) {
            this.id = HugeStreamWithThreads.this.id;
            message = HugeStreamWithThreads.this.message;
        }
    }
    if(message == null)
        message = ":keep-alive\n\n";
    else
        message = formatMessage(message);

    final Thread curr = Thread.currentThread();
    Thread canceller = new Thread(new Runnable() {
        public void run()
        {
            try {
                Thread.sleep(2000);
                curr.interrupt();
            }
            catch(InterruptedException e) {
                // exit
            }
        }
    });
    canceller.start();

    try {
        if(!sendMessage(message))
            return;
    } finally {
        canceller.interrupt();
        while (true) {
            try { canceller.join(); break; }
            catch (InterruptedException e) { }
        }
    }

    boolean once_again = false;
    synchronized(HugeStreamWithThreads.this) {
        if(this.id != HugeStreamWithThreads.this.id)
            once_again = true;
    }
    if(once_again)
        pool.submit(this);

}