Tomcat源码分析使用NIO接收HTTP请求(一)----简单实现Acceptor、Poller、PollerEvent

注:本文基于Tomcat8.5撰写

下面的代码主要功能是使用NIO来接收一次http请求,主要包括三个步骤:

 1.打开服务器通道接收请求

 2.接收请求后注册通道 

 3.输出请求的内容。

这个示例是极其简单的,在下一小节笔者会在此示例基础上来探讨Tomcat是如何接收http请求的。

public static void main(String[] args) {
    try {
        // 打开服务器套接字通道
        ServerSocketChannel serverSock = ServerSocketChannel.open();
        // 绑定端口
        InetSocketAddress address = new InetSocketAddress(8001);
        serverSock.socket().bind(address);
        // 设置阻塞模式
        serverSock.configureBlocking(true);
        SocketChannel socket;
        Selector selector = Selector.open();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1000);

        while(true) {
            // 接收请求
            socket = serverSock.accept();
            socket.configureBlocking(false);
            // 注册通道
            socket.register(selector, SelectionKey.OP_READ);
            int keyCount = selector.selectNow();

            // 处理请求
            Iterator<SelectionKey> iterator = null;
            if ( keyCount > 0) {
                iterator = selector.selectedKeys().iterator();
            }
            while (iterator != null && iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isReadable()) {
                        key.interestOps(key.interestOps() & (~key.readyOps()));
                        SocketChannel channel = (SocketChannel) key.channel();
                        boolean flag = true;
                        while (flag) {
                            int count = channel.read(byteBuffer);
                            if (count > 0) {
                                flag = false;
                                // 解析请求
                                System.out.println(new String(byteBuffer.array()));
                            }
                            Thread.sleep(2000);
                        }
                    }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Tomcat接收Http请求

在上一小节中介绍了使用NIO接收Http请求的三个主要步骤,第一步是很简单的,除了使用的bind方法不同以外,其余并没有太大差别。这里着重探讨一下第二步,即Tomcat如何接收一个Http请求。Tomcat使用NIO接收请求的类叫NioEndpoint,在这个类中定义了五个内部类用来处理请求。第一个类叫Acceptor,它的主要作用是等待连接(SocketChannel),当有连接到来时Tomcat会使用PollerEvent对连接进行注册,注册后Tomcat会使用Poller处理请求事件,当Poller获取到请求事件后会将请求交给SocketProcessor类进行解析。以上对Tomcat处理请求的文字描述所对应的代码思路就是上一小节while循环中的注释。到目前为止我们已经知道了四个类,第五个类是NioSocketWrapper,这个主要用来包装SocketChannel。Tomcat会将SocketChannel包装成NioChannel进而在包装成NioSocketWrapper,至此Tomcat处理http请求所需要的五个类就介绍完了,接下来让我们来写几行代码,来实现上述逻辑。

实现Acceptor、Poller、PollerEvent

首先让我们先将目光聚焦在Acceptor、Poller、PollerEvent这三个内部组件。接下来要写的代码主要 目的是理解这三个组件,下面这张图简单描述了我们即将要开始的示例程序。 

 首先新建一个简单的java工程即可,并新建一个名为NioEndpoint的类。
public class NioEndpoint {
    protected volatile ServerSocketChannel serverSock = null;
    protected int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
    protected Poller[] pollers = null;
    private AtomicInteger pollerRotater = new AtomicInteger(0);
    public static final int OP_REGISTER = 0x100;
    ......
    public Poller getPoller0() {
        int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
        return pollers[idx];
    }
    ......
}

第二步:新建一个bind方法

protected void bind() {
    try {
        // 打开服务器套接字通道
        serverSock = ServerSocketChannel.open();
        // 绑定端口
        InetSocketAddress address = new InetSocketAddress(8000);
        int acceptCount = 10;
        serverSock.socket().bind(address, acceptCount);
        // 设置阻塞模式
        serverSock.configureBlocking(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

第三步:新建Acceptor内部类

protected class Acceptor implements Runnable{
    @Override
    public void run() {
        SocketChannel socket = null;
        while (true) {
            try {
                // 接收请求
                socket = serverSock.accept();
                setSocketOptions(socket);
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

第四步:新建setSocketOptions()方法

protected void setSocketOptions(SocketChannel socket) {
    try {
        socket.configureBlocking(false);
        getPoller0().register(socket);
    } catch (Exception e) {
    }
}

第五步:新建SynchronizedQueue类(这个不是内部类)

public class SynchronizedQueue<T> {

    public static final int DEFAULT_SIZE = 128;

    private Object[] queue;
    private int size;
    private int insert = 0;
    private int remove = 0;

    public SynchronizedQueue() {
        this(DEFAULT_SIZE);
    }

    public SynchronizedQueue(int initialSize) {
        queue = new Object[initialSize];
        size = initialSize;
    }

    public synchronized boolean offer(T t) {
        queue[insert++] = t;

        // Wrap
        if (insert == size) {
            insert = 0;
        }

        if (insert == remove) {
            expand();
        }
        return true;
    }

    public synchronized T poll() {
        if (insert == remove) {
            // empty
            return null;
        }

        @SuppressWarnings("unchecked")
        T result = (T) queue[remove];
        queue[remove] = null;
        remove++;

        // Wrap
        if (remove == size) {
            remove = 0;
        }

        return result;
    }

    private void expand() {
        int newSize = size * 2;
        Object[] newQueue = new Object[newSize];

        System.arraycopy(queue, insert, newQueue, 0, size - insert);
        System.arraycopy(queue, 0, newQueue, size - insert, insert);

        insert = size;
        remove = 0;
        queue = newQueue;
        size = newSize;
    }

    public synchronized int size() {
        int result = insert - remove;
        if (result < 0) {
            result += size;
        }
        return result;
    }

    public synchronized void clear() {
        queue = new Object[size];
        insert = 0;
        remove = 0;
    }
}

第六步:新建Poller内部类

public class Poller implements Runnable {
    private Selector selector;
    private volatile int keyCount = 0;
    private final SynchronizedQueue<PollerEvent> events =
            new SynchronizedQueue<>();

    public Poller() throws IOException {
        this.selector = Selector.open();
    }

    @Override
    public void run() {
        try {
            while (true) {
                events();
                keyCount = selector.selectNow();
                Iterator<SelectionKey> iterator = null;
                if ( keyCount > 0) {
                    iterator = selector.selectedKeys().iterator();
                }
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    iterator.remove();
                    SocketChannel socket = (SocketChannel) sk.attachment();
                    // 得到socket用于进行后续处理......
                    System.out.println(socket.socket());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void register(final SocketChannel socket) {
        PollerEvent r = new PollerEvent(socket, this,OP_REGISTER);
        addEvent(r);
    }

    private void addEvent(PollerEvent event) {
        events.offer(event);
    }

    public boolean events() {
        boolean result = false;

        PollerEvent pe = null;
        for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
            result = true;
            try {
                pe.run();
                pe.reset();
            } catch ( Throwable x ) {
            }
        }

        return result;
    }

    public Selector getSelector() {
        return selector;
    }
}

第七步:新建PollerEvent内部类

public static class PollerEvent implements Runnable {
    private SocketChannel socket;
    private int interestOps;
    private Poller poller;

    public PollerEvent(SocketChannel socket,Poller poller, int intOps) {
        reset(socket, poller, intOps);
    }

    @Override
    public void run() {
        if (interestOps == OP_REGISTER) {
            try {
                socket.register(
                        poller.getSelector(), SelectionKey.OP_READ, socket);
            } catch (Exception e) {
            }
        }
    }

    public void reset(SocketChannel socket, Poller poller, int intOps) {
        this.socket = socket;
        interestOps = intOps;
        this.poller = poller;
    }

    public void reset() {
        reset(null, null, 0);
    }
}

第八步:实现main方法

public static void main(String[] args) {
    try {
        NioEndpoint server = new NioEndpoint();

        server.bind();

        server.pollers = new Poller[server.pollerThreadCount];
        for (int i=0; i<server.pollers.length; i++) {
            server.pollers[i] = server.new Poller();
            Thread pollerThread = new Thread(server.pollers[i]);
            // 设置线程优先级
            pollerThread.setPriority(5);
            // 设置守护线程
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

        Runnable acceptor = server.new Acceptor();
        new Thread(acceptor).start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

运行main方法后发送http://localhost:8000/请求结果如下图,在这里仅仅是打印了socket对象。

 上面的示例程序是根据Tomcat源码抽简而来,其主要目的是想让读者理解Acceptor、Poller、PollerEvent这三个组件是如何相互影响的。下面的时序图展示一次请求被程序处理的过程。

在示例程序中,我们在main方法中创建了Acceptor等组件,而在Tomcat源码中是在startInternal()方法中创建组件的。读者可以先从该方法开始阅读Tomcat源码,并且把当下的阅读重点放在Acceptor、Poller、PollerEvent这三个组件是如何接收一次请求的,先从整体上有一个大致了解为之后打下基础。在接下来我们需要继续来完善上述代码。

 

结束!!!

posted @ 2022-11-17 17:18  一十三  阅读(967)  评论(1)    收藏  举报