如何使用异步servlet+;文件下载的非阻塞IO?

如何使用异步servlet+;文件下载的非阻塞IO?,io,java-ee-7,servlet-3.1,Io,Java Ee 7,Servlet 3.1,我的图像文件存储在数据库中(我知道它们不应该,但也帮不了忙)。 为了能够在客户机上呈现它们,我实现了一个异步servlet,它帮助从数据库列读取二进制流,并写入servlet响应的输出流。传统IO在这里工作得很好 当我想到用异步servlet尝试非阻塞IO(以测试性能)时,我在响应中返回的二进制数据不断被破坏 从开始,我看到了各种使用异步NIOServlet上传文件的示例,但对我的问题没有帮助 以下是servlet代码: @WebServlet(asyncSupported = true, ur

我的图像文件存储在数据库中(我知道它们不应该,但也帮不了忙)。 为了能够在客户机上呈现它们,我实现了一个异步servlet,它帮助从数据库列读取二进制流,并写入servlet响应的输出流。传统IO在这里工作得很好

当我想到用异步servlet尝试非阻塞IO(以测试性能)时,我在响应中返回的二进制数据不断被破坏

从开始,我看到了各种使用异步NIOServlet上传文件的示例,但对我的问题没有帮助

以下是servlet代码:

@WebServlet(asyncSupported = true, urlPatterns = "/myDownloadServlet")
public class FileRetrievalServletAsyncNIO extends HttpServlet
{
    private static final long serialVersionUID = -6914766655133758332L;

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        Queue<byte[]> containerQueue = new LinkedList<byte[]>();

        AsyncContext asyncContext = request.startAsync();
        asyncContext.addListener(new AsyncListenerImpl());
        asyncContext.setTimeout(120000);

        try
        {
            long attachmentId = Long.valueOf(request.getParameter("id"));
            MyAttachmentDataObject retObj = ServletUtils.fetchAttachmentHeaders(attachmentId);

            response = (HttpServletResponse) asyncContext.getResponse();
            response.setHeader("Content-Length", String.valueOf(retObj.getContentLength()));
            if (Boolean.valueOf(request.getParameter(ServletConstants.REQ_PARAM_ENABLE_DOWNLOAD)))
                response.setHeader("Content-disposition", "attachment; filename=" + retObj.getName());
            response.setContentType(retObj.getContentType());
            ServletOutputStream sos = response.getOutputStream();
            ServletUtils.fetchContentStreamInChunks(attachmentId, containerQueue); // reads from database and adds to the queue in chunks
            sos.setWriteListener(new WriteListenerImpl(sos, containerQueue, asyncContext));
        }
        catch (NumberFormatException | IOException exc)
        {
            exc.printStackTrace();
            request.setAttribute("message", "Failed");
        }
    }
}
@WebServlet(asyncSupported=true,urlPatterns=“/myDownloadServlet”)
公共类FileRetrievalServletAsynnio扩展了HttpServlet
{
私有静态最终长serialVersionUID=-69147666551337375832L;
@凌驾
受保护的无效服务(HttpServletRequest请求、HttpServletResponse响应)引发ServletException、IOException
{
Queue containerQueue=new LinkedList();
AsyncContext AsyncContext=request.startAsync();
addListener(新的AsyncListenerImpl());
asyncContext.setTimeout(120000);
尝试
{
long attachmentId=long.valueOf(request.getParameter(“id”);
MyAttachmentDataObject retObj=ServletUtils.fetchAttachmentHeaders(attachmentId);
response=(HttpServletResponse)asyncContext.getResponse();
setHeader(“Content-Length”,String.valueOf(retObj.getContentLength());
if(Boolean.valueOf(request.getParameter(ServletConstants.REQ\u PARAM\u ENABLE\u DOWNLOAD)))
response.setHeader(“内容处置”、“附件;文件名=“+retObj.getName());
response.setContentType(retObj.getContentType());
ServletOutputStream sos=response.getOutputStream();
ServletUtils.fetchContentStreamInChunks(attachmentId,containerQueue);//从数据库读取数据并以块的形式添加到队列中
setWriteListener(新的WriteListenerImpl(sos,containerQueue,asyncContext));
}
捕获(NumberFormatException | IOException exc)
{
exc.printStackTrace();
setAttribute(“消息”,“失败”);
}
}
}
下面是写侦听器实现

public class WriteListenerImpl implements WriteListener
{

    private ServletOutputStream output = null;
    private Queue<byte[]> queue = null;
    private AsyncContext asyncContext = null;
    private HttpServletRequest request = null;
    private HttpServletResponse response = null;

    public WriteListenerImpl(ServletOutputStream sos, Queue<byte[]> q, AsyncContext aCtx)
    {
        output = sos;
        queue = q;
        asyncContext = aCtx;
        request = (HttpServletRequest) asyncContext.getRequest();
    }

    @Override
    public void onWritePossible() throws IOException
    {
        while(output.isReady())
        {
            while (!queue.isEmpty())
            {
                byte[] temp = queue.poll();
                output.write(temp, 0, temp.length);
            }

            asyncContext.complete();
            request.setAttribute("message", "Success");
        }
    }

    @Override
    public void onError(Throwable t)
    {
        System.err.println(t);
        try
        {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        catch (IOException exc)
        {
            exc.printStackTrace();
        }
        request.setAttribute("message", "Failure");
        asyncContext.complete();
    }
}
公共类WriteListenerImpl实现WriteListener
{
私有ServletOutputStream输出=null;
专用队列=null;
私有AsyncContext AsyncContext=null;
私有HttpServletRequest请求=null;
私有HttpServletResponse=null;
公共WriteListenerImpl(ServletOutputStream sos、队列q、AsyncContext aCtx)
{
输出=sos;
队列=q;
asyncContext=aCtx;
请求=(HttpServletRequest)asyncContext.getRequest();
}
@凌驾
public void onWritePossible()引发IOException
{
while(output.isReady())
{
而(!queue.isEmpty())
{
字节[]temp=queue.poll();
输出写入(温度、0、温度长度);
}
asyncContext.complete();
setAttribute(“消息”、“成功”);
}
}
@凌驾
公共作废登记员(可丢弃的t)
{
系统错误println(t);
尝试
{
senderError(HttpServletResponse.SC_内部_服务器_错误);
}
捕获(IOException)
{
exc.printStackTrace();
}
setAttribute(“消息”、“失败”);
asyncContext.complete();
}
}
响应数据如下所示:


我做错了什么?

不确定您希望输出是什么样子的,但在异步I/o方面,您应该在每次写入之前检查output.isReady()。因此,您的onWritePossible代码应该是:

while(output.isReady() && !queue.isEmpty())
 {
       byte[] temp = queue.poll();
       output.write(temp, 0, temp.length);
 }

 if (queue.isEmpty()) {
      asyncContext.complete();
      request.setAttribute("message", "Success");
 }
这允许onWritePossible()在写入被阻止时返回,这是异步I/O的基本点


如果在写入被阻止时写入(output.isReady()将返回false),则不同的实现可能会忽略写入或引发异常。无论哪种方式,你的输出数据都会丢失一些中间写或截断。 不,那没用。下载的文件不正确。文件有什么问题?在尝试使用异步io之前,您是如何写入该文件的?该文件没有问题。下载与异步servlet+传统IO完美结合。只有NIO因为某种原因不能工作。下载有什么问题?另外,如果您更改代码,使servlet在不使用writeListener的情况下循环遍历数据并写入一天,那么它工作正常吗?是的,没有write listener,一切都工作正常。另外,Wildfly和Tomcat的servlet实现之间存在一些细微的差异。还是不知道问题出在哪里。我想我得挖得更深一些。