前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Tomcat中BIO与NIO

Tomcat中BIO与NIO

作者头像
加多
发布2018-09-06 14:54:58
1.4K0
发布2018-09-06 14:54:58
举报
文章被收录于专栏:Java编程技术Java编程技术

一、 前言

二、 Connector

研究过tomcat的童鞋应该都知道tomcat的容器构造:

image.png

Connector是一个桥梁它把Server和Engine链接了起来,Connector的作用是接受客户端端的请求,然后把请求委托为engine容器去处理。 Connector内部使用endpoint进行处理,根据处理方式的不同分为NioEndpoint,JIoEndpoint,AprEndpoint。

image.png

三、 NioEndpoint

3.1 主流程启动时序

image.png

下面首先分析下套接字绑定代码:

代码语言:javascript
复制
 public void bind() throws Exception {

        //创建服务套接字
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getBacklog());
        serverSock.configureBlocking(true); //打开阻塞模式
        serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());

        // 初始化接受线程个数和轮询链接套接字状态线程个数
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }
        stopLatch = new CountDownLatch(pollerThreadCount);

        // Initialize SSL if needed
        if (isSSLEnabled()) {
            SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);

            sslContext = sslUtil.createSSLContext();
            sslContext.init(wrap(sslUtil.getKeyManagers()),
                    sslUtil.getTrustManagers(), null);

            SSLSessionContext sessionContext =
                sslContext.getServerSessionContext();
            if (sessionContext != null) {
                sslUtil.configureSessionContext(sessionContext);
            }
            // Determine which cipher suites and protocols to enable
            enabledCiphers = sslUtil.getEnableableCiphers(sslContext);
            enabledProtocols = sslUtil.getEnableableProtocols(sslContext);
        }

        if (oomParachute>0) reclaimParachute(true);
        selectorPool.open();
    }

启动startInternal代码:

代码语言:javascript
复制
 public void startInternal() throws Exception {

        if (!running) {
            running = true;
            paused = false;

            // 创建处理线程池
            if ( getExecutor() == null ) {
                createExecutor();
            }

            initializeConnectionLatch();

            //启动poller 线程,nio需要
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }

            //启动链接接受线程
            startAcceptorThreads();
        }
    }

需要注意的是startAcceptorThreads里面可以创建多个线程去调用 serverSock.accept()来接受完成了三次握手的链接的socket,但是为什么这里会有多个线程那?平时我们不都是用一个线程?因为这个方法是阻塞的,那么多个线程去接受链接有好处?

答案是肯定的,因为服务套接字调用listen方法后,就可以接受链接了,它会吧接受到了链接放入到TCP缓存队列,当调用accept时候是从这个缓存队列里面获取一个已经完成三次握手的套接字处理,而这个缓存大小是受acceptcount影响的。

3.2 Acceptor线程

accept线程作用是接受客户端发来的请求并放入到事件队列。

image.png

看下代码:

代码语言:javascript
复制
protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
        public void run() {

            int errorDelay = 0;

            // 一直循环直到接收到shutdown命令
            while (running) {

                ...

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //如果达到max connections个请求则等待
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
                        // 从TCP缓存获取一个完成三次握手的套接字,没有则阻塞
                        // socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                        ...
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                   ....
                } catch (SocketTimeoutException sx) {
                    // Ignore: Normal condition
                ....
            }
            state = AcceptorState.ENDED;
        }
    }

3.3 Poll线程

poll线程作用是从事件队列里面获取事件把链接套接字加入selector,并且监听socket事件进行处理。

image.png

代码语言:javascript
复制
public void run() {
    while (true) {
        try {
            ...
            if (close) {
               ...
            } else {
                hasEvents = events();
            }
            try {
                ...
            } catch ( NullPointerException x ) {...
            }
         
            Iterator<SelectionKey> iterator =
                keyCount > 0 ? selector.selectedKeys().iterator() : null;
            // 遍历所有注册的channel对感兴趣的事件处理
            while (iterator != null && iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                KeyAttachment attachment = (KeyAttachment)sk.attachment();
               
                if (attachment == null) {
                    iterator.remove();
                } else {
                    attachment.access();
                    iterator.remove();
                    processKey(sk, attachment);
                }
            }//while

            //process timeouts
            timeout(keyCount,hasEvents);
            if ( oomParachute > 0 && oomParachuteData == null ) checkParachute();
        } catch (OutOfMemoryError oom) {
            ...
        }
    }//while
    synchronized (this) {
        this.notifyAll();
    }
    stopLatch.countDown();

}
代码语言:javascript
复制
//如配置线程池则请求交给线程池处理。
public boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
    try {
        KeyAttachment attachment = (KeyAttachment)socket.getAttachment();
        if (attachment == null) {
            return false;
        }
        attachment.setCometNotify(false); //will get reset upon next reg
        SocketProcessor sc = processorCache.poll();
        if ( sc == null ) sc = new SocketProcessor(socket,status);
        else sc.reset(socket,status);
        if ( dispatch && getExecutor()!=null ) getExecutor().execute(sc);
        else sc.run();
    } catch (RejectedExecutionException rx) {
       ...
    }
    return true;
}

3.4 总结

NIO 是可以多个线程来接受客户端的链接,这个和bio是一样的,不一样在于是NIO会把接受到的链接放入事件队列,然后多个poll线程会从事件队列获取事件,并且NIO可以每个poll线程去监听多个链接socket的事件,然后交给线程池去处理,也就说一个poll 线程可以监听多个socket的读写事件,然后交给线程池去处理,这相比于bio节省了很多线程资源。

四、JioEndpoint

4.1 主启动流程

image.png

首先看下bind函数有啥不同

代码语言:javascript
复制
public void bind() throws Exception {

    // Initialize thread count defaults for acceptor
    if (acceptorThreadCount == 0) {
        acceptorThreadCount = 1;
    }
    // Initialize maxConnections
    if (getMaxConnections() == 0) {
        // User hasn't set a value - use the default
        setMaxConnections(getMaxThreadsExecutor(true));
    }
    
    //创建serversocket工厂
    if (serverSocketFactory == null) {
        if (isSSLEnabled()) {
            serverSocketFactory =
                handler.getSslImplementation().getServerSocketFactory(this);
        } else {
            serverSocketFactory = new DefaultServerSocketFactory(this);
        }
    }

    //创建serversocket
    if (serverSocket == null) {
        try {
            if (getAddress() == null) {
                serverSocket = serverSocketFactory.createSocket(getPort(),
                        getBacklog());
            } else {
                serverSocket = serverSocketFactory.createSocket(getPort(),
                        getBacklog(), getAddress());
            }
        } catch (BindException orig) {
            String msg;
            if (getAddress() == null)
                msg = orig.getMessage() + " <null>:" + getPort();
            else
                msg = orig.getMessage() + " " +
                        getAddress().toString() + ":" + getPort();
            BindException be = new BindException(msg);
            be.initCause(orig);
            throw be;
        }
    }

}

内部实际是调用new ServerSocket 创建seversocket.

4.2 Acceptor线程

image.png

代码如下:

代码语言:javascript
复制
    protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

               ...
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    Socket socket = null;
                    try {
                        //接受链接
                        socket = serverSocketFactory.acceptSocket(serverSocket);
                    } catch (IOException ioe) {
                       。。。
                    }
                  
                    //配置套接字选项
                    if (running && !paused && setSocketOptions(socket)) {
                        // 处理套接字
                        if (!processSocket(socket)) {
                            countDownConnection();
                            // Close socket right away
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        // Close socket right away
                        closeSocket(socket);
                    }
                } catch (IOException x) {
                   。。。
                }
            }
            state = AcceptorState.ENDED;
        }
    }

把设置好套接字选项后的套接字放入线程池处理

代码语言:javascript
复制
 protected boolean processSocket(Socket socket) {
        // Process the request from this socket
        try {
            SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
            wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
            wrapper.setSecure(isSSLEnabled());
            // During shutdown, executor may be null - avoid NPE
            if (!running) {
                return false;
            }
            getExecutor().execute(new SocketProcessor(wrapper));
        } catch (RejectedExecutionException x) ...
        }
        return true;
    }

4.3 总结

可知BIO也可以使用多线程去接受链接,然后把接受的socket放入线程池进行处理,是每个线程处理一个链接sockcet,这是典型的处理方式。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.06.01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 前言
  • 二、 Connector
  • 三、 NioEndpoint
    • 3.1 主流程启动时序
      • 3.2 Acceptor线程
        • 3.3 Poll线程
          • 3.4 总结
      • 四、JioEndpoint
        • 4.1 主启动流程
          • 4.2 Acceptor线程
            • 4.3 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档