初探连接器的奥秘之NIO如何具体实现(二)

初探连接器的奥秘之NIO如何具体实现

前面我们聊到Tomcat有两大核心,HTTP 服务器 + Servlet 容器,那么他们分别对应的作用是连接器和容器

  • 连接器负责接收请求
  • 容器负责处理请求

在这先聊聊连接器,我们将连接器的功能细化。

  1. 监听端口
  2. 接收连接请求
  3. 读取请求网络字节流
  4. 根据应用层协议解析
  5. 交给容器
  6. 响应给浏览器

我们着重先看前两步骤,首先监听端口和接收请求。

Tomcat设计了AbstractEndpoint组件来处理这两步,AbstractEndpoint相当于一个通信端口,接收具体的Socket和发送,是对传输层的抽象,实现了TCP/IP协议。

其具体的实现有如下:

在这里插入图片描述

我们注意到NioEndpoint,就是本节我们要着重关注的。

大致过程:

  • 在AbstractEndpoint有个重要的属性Acceptor,负责监听请求;
  • 在NioEndpoint也有两大重要的子组件:Poller和SocketProcessor,Poller 实现Runnable负责轮训已经接收的请求(是否内核数据已经准备好),其中SocketProcessor处理socket请求,丢给线程池。

在这里插入图片描述

Acceptor监听

Acceptor监听到请求,register进去Poller中的队列中;

public class Acceptor<U> implements Runnable {

    private final AbstractEndpoint endpoint;
    private volatile boolean stopCalled = false;

    public Acceptor(AbstractEndpoint endpoint) {
        this.endpoint = endpoint;
    }

    @SuppressWarnings("deprecation")
    @Override
    public void run() {
        try {
            while (!stopCalled) {
              	//接收请求
                SocketChannel socket = endpoint.serverSocketAccept();
                //注册
                endpoint.setSocketOptions(socket);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

设置一些属性,包装成NioSocketWrapper

@Override
protected boolean setSocketOptions(SocketChannel socket) {
    //包装类
    NioSocketWrapper socketWrapper = null;
    try {
        .......。。
				.......。。。
        // Set socket properties
        // Disable blocking, polling will be used
        //设置非阻塞
        socket.configureBlocking(false);
        socketProperties.setProperties(socket.socket());
        //设置读超时时间
        socketWrapper.setReadTimeout(getConnectionTimeout());
        //设置写超时时间
        socketWrapper.setWriteTimeout(getConnectionTimeout());
        //设置长连接时间
        socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
        //注册到队列中
        poller.register(socketWrapper);
        return true;
    } catch (Throwable t) {
    }
    // Tell to close the socket if needed
    return false;
}

注册到队列

public void register(final NioSocketWrapper socketWrapper) {
    socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
    PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
    addEvent(pollerEvent);
}
private void addEvent(PollerEvent event) {
    events.offer(event);
}

SynchronizedQueue队列是数组实现。

用简单的结构减少维护节点的开销,并且整个过程只会offer和poll

private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();

Poller轮询事件

请求包装成PollerEvent事件,注册到队列后,Poller线程进行轮询

在这里插入图片描述

最后将socket封装成socketProcessor提交到Executor处理

public void run() {
    while (true) {
      	。。。。
        try {
            if (!close) {
                //遍历events,绑定selector
                hasEvents = events();
                //获取就绪IO数量
                keyCount = selector.select(selectorTimeout);
            }
        } catch (Throwable x) {
        }
				//当有就绪IO事件时,再遍历selectedKeys
        Iterator<SelectionKey> iterator =
            keyCount > 0 ? selector.selectedKeys().iterator() : null;
        
        while (iterator != null && iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            iterator.remove();
            NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
            
            if (socketWrapper != null) {
              	交给processKey函数,判断是read还是write事件
                processKey(sk, socketWrapper);
            }
        }
				。。。。。。
    }
}

丢给线程池

拿到就绪IO的Socket,到processKey函数处理,根据读还是写调用processSocket处理

protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
    try {
        if (close) {
            cancelledKey(sk, socketWrapper);
        } else if (sk.isValid()) {
            if (sk.isReadable() || sk.isWritable()) {
                if (socketWrapper.getSendfileData() != null) {
                    processSendfile(sk, socketWrapper, false);
                } else {
                    。。。
                    // Read goes before write
                    // 读优于写
                    if (sk.isReadable()) {
                        。。。
                            //processSocket处理可读请求
                        } else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
                            closeSocket = true;
                        }
                    }
                    if (!closeSocket && sk.isWritable()) {
                       。。。。
                            //processSocket处理可写请求
                        } else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) {
                            closeSocket = true;
                        }
 								。。。。。。
    }
}

processSocket方法创建一个SocketProcessor丢给线程池

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
        SocketEvent event, boolean dispatch) {
    try {
        if (socketWrapper == null) {
            return false;
        }
        SocketProcessorBase<S> sc = null;
        if (processorCache != null) {
            sc = processorCache.pop();
        }
        if (sc == null) {
            // 创建一个 SocketProcessor 的实例
            sc = createSocketProcessor(socketWrapper, event);
        } else {
            sc.reset(socketWrapper, event);
        }
        //线程池(默认有)
        Executor executor = getExecutor();
        //dispatch读写事件都是true,像ERROR或STOP事件都是false
        if (dispatch && executor != null) {
            executor.execute(sc);
        } else {
            sc.run();
        }
			。。。。。
    return true;
}

总结

总结一下目前分析结果,Acceptor监听到请求后,封装成PollerEvent注册到Poller中的队列里;Poller的run方法去轮训队列的事件,绑定到Selector选择器上,然后selector.select去获取就绪IO,包装创建SocketProcessor到线程池。这是本节主要的内容,后续可以自己去debug看看源码更详细的实现,这也是Tomcat对于IO模型中NIO的具体实现。

从监听到经过一系列的处理交给线程池,后续如果具体请求如何到容器呢?看下节分析!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值