2

下面的代码是 servlet 3.1 Non Blocking IO demo:

上传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 有什么区别?我想知道上面的代码是如何工作的。

4

1 回答 1

1

读取输入数据:

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

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

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

在同一个例子中,这次线程读取数据并返回,10秒后被唤醒,读取下一个数据并返回,10秒后被唤醒,读取数据并返回等等。这一次,虽然它仍然花了2读取数据所需的线程仅在不到一秒的时间内处于活动状态,并且可用于其他工作。因此,虽然特定请求仍然需要 2 分钟,但拥有 10 个线程的服务器现在可以每 2 分钟处理更多请求。

发送响应数据:

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

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

于 2016-01-13T19:05:07.133 回答