java - 关于 Servlet 3.1 非阻塞 IO 示例的问题

标签 java nonblocking servlet-3.1

以下代码是servlet 3.1非阻塞IO演示:

上传Servlet:

@WebServlet(name = "UploadServlet", urlPatterns = {"/UploadServlet"}, asyncSupported=true)
public class UploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        AsyncContext context = request.startAsync();
        // set up async listener
        context.addListener(new AsyncListener() {
            public void onComplete(AsyncEvent event) throws IOException {
                event.getSuppliedResponse().getOutputStream().print("Complete");

            }

            public void onError(AsyncEvent event) {
                System.out.println(event.getThrowable());
            }

            public void onStartAsync(AsyncEvent event) {
            }

            public void onTimeout(AsyncEvent event) {
                System.out.println("my asyncListener.onTimeout");
            }
        });
        ServletInputStream input = request.getInputStream();
        ReadListener readListener = new ReadListenerImpl(input, response, context);
        input.setReadListener(readListener);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

RealListenerImpl:

public class ReadListenerImpl implements ReadListener{
    private ServletInputStream input = null;
    private HttpServletResponse res = null;
    private AsyncContext ac = null;
    private Queue queue = new LinkedBlockingQueue();
    ReadListenerImpl(ServletInputStream in, HttpServletResponse r, AsyncContext c) {
        input = in;
        res = r;
        ac = c;
    }
    public void onDataAvailable() throws IOException {
        System.out.println("Data is available");

        StringBuilder sb = new StringBuilder();
        int len = -1;
        byte b[] = new byte[1024];
        while (input.isReady() && (len = input.read(b)) != -1) {
            String data = new String(b, 0, len);
            sb.append(data);
        }
        queue.add(sb.toString());
    }
    public void onAllDataRead() throws IOException {
        System.out.println("Data is all read");

        // now all data are read, set up a WriteListener to write
        ServletOutputStream output = res.getOutputStream();
        WriteListener writeListener = new WriteListenerImpl(output, queue, ac);
        output.setWriteListener(writeListener);
    }
    public void onError(final Throwable t) {
        ac.complete();
        t.printStackTrace();
    }
}

WriteListenerImpl:

public class WriteListenerImpl implements WriteListener{
    private ServletOutputStream output = null;
    private Queue queue = null;
    private AsyncContext context = null;

    WriteListenerImpl(ServletOutputStream sos, Queue q, AsyncContext c) {
        output = sos;
        queue = q;
        context = c;
    }

    public void onWritePossible() throws IOException {
        while (queue.peek() != null && output.isReady()) {
            String data = (String) queue.poll();
            output.print(data);
        }
        if (queue.peek() == null) {
            context.complete();
        }
    }

    public void onError(final Throwable t) {
        context.complete();
        t.printStackTrace();
    }
}

上面的代码工作正常,我想知道与阻塞 IO servlet 有什么区别?我想知道上面的代码是如何工作的。

最佳答案

读取输入数据:

在阻塞场景中,当您从输入流读取数据时,每次读取都会阻塞,直到数据可用为止。对于发送大量数据的远程客户端来说,这可能会花费很长时间,这意味着线程会保持很长时间。

例如,考虑在 2 分钟内定期接收 13 个 block 的入站数据。在阻塞读取中,您读取第一个 block ,将线程保持约 10 秒,读取下一个 block ,将线程保持约 10 秒,等等。在这种情况下,线程实际处理数据的时间可能不到一秒,并且阻塞了近 120 秒等待数据。然后,如果您的服务器有 10 个线程,您可以看到每 2 分钟就有 10 个客户端的吞吐量。

在非阻塞场景中,当 isReady() 返回 true 时,readListener 读取数据(每次调用读取数据之前必须检查 isReady()),但当 isReady() 返回 false 时,readListener 返回并放弃线程。然后,当更多数据到达时,调用 onDataAvailable(),并且 readListener 再次读取数据,直到 isReady 为 false()。

同一个例子,这次线程读取数据并返回,10秒后被唤醒,读取下一个数据并返回,10秒后被唤醒读取数据并返回等等。仍然需要 2 分钟来读取数据,执行此操作所需的线程仅 Activity 不到一秒,并且可用于其他工作。因此,虽然特定请求仍然需要 2 分钟,但具有 10 个线程的服务器现在可以每 2 分钟处理更多请求。

发送响应数据:

该场景与发送数据类似,并且在发送大型响应时很有用。例如,在阻塞场景中发送 13 个 block 的大型响应可能需要 2 分钟才能发送,因为客户端需要 10 秒来确认收到每个 block ,并且线程在等待时被保留。然而,在非阻塞场景中,线程仅在发送数据时被持有,而不是在等待能够再次发送时被持有。因此,对于特定的客户端,响应的发送速度不会更快,但线程会保留一小部分时间,并且处理请求的服务器的吞吐量会显着增加。

所以这里的例子是人为的,但只是为了说明一点。关键是非阻塞 I/O 发出单个请求的速度并不比阻塞 I/O 快,但当应用程序读取输入数据的速度比客户端发送数据的速度和/或发送响应数据的速度更快时,会增加服务器吞吐量比客户端可以接收它。

关于java - 关于 Servlet 3.1 非阻塞 IO 示例的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34104835/

相关文章:

java - Jenkins 将构建参数传递给 email-ext 模板

java - Maven 无法使用 Eclipse 解决 Jacob 依赖关系

java - Bluemix Liberty Java 不使用 Java 8 构建

java - Tomcat主页(地址:8080) redirects to website homepage

multithreading - 多线程意义上的机器指令假设

c - 如何设置文件描述符非阻塞?

python - 从多个子进程进行非阻塞读取(Python)

google-app-engine - App Engine Java 8 标准环境下的 Servlet 异步处理支持