Tomcat架构解析(六)-----BIO、NIO、NIO2、APR

对于应用服务器来说,性能是非常重要的,基本可以说决定着这款应用服务器的未来。通常从软件角度来说,应用服务器性能包括如下几个方面:

1、请求处理的并发程度,当前主流服务器均采用异步的方式处理客户端的请求;

2、减少网络传输的数据量,提高网络利用率;

3、降低新建网络链接的开销,以实现链接在多个请求之间的复用;

4、选择合适的I/O方式,例如NIO等。

一、阻塞与非阻塞、同步与异步

                                       ------同步:发出一个调用时,没有得到结果之前,该调用不返回,由调用者主动等待调用结果。
                                      |
 关注的是消息通信机制---------------------|
                                      |
                                       ------异步:调用发出之后,调用直接返回,此时不会拿到返回结果。被调用者通过状态通知调用者或回调函数处理这个调用。

                                       ------阻塞:调用结果返回之前,当前线程会被挂起。
                                      |
 关注的是程序在等待调用结果时的状态---------|
                                      |
                                       ------非阻塞:调用返回结果之前,当前线程不会被挂起。

二、BIO

  概念:bio基于流,是同步阻塞IO模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一      个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。不知道io操作中什么时候有数据可读,所以一直是阻塞的模式。

缺点:当并发数达到一定量时,并且服务端需要一定的时间去处理请求时,例如1-2s,这时需要开启非常多的线程数,并且这些线程啥事不干,都是等着请求返回,大大浪费了系统资源,而且在线程切换上下文的过程中,也会浪费很多的资源

BIO是阻塞式I/O,通过socket在客户端与服务端建立双向链接以实现通信,主要步骤如下:

a、服务端监听某个端口是否有链接请求;

b、客户端向服务端发出链接请求;

c、服务端向客户端返回accept()消息,此时链接成功;

d、客户端和服务端通过send()、write()等方法与对方通信;

e、关闭链接

eg:简单的网络通信如下:

服务端:

客户端:

这种简单的示例只支持一个客户端链接到一个服务端,现实情况是N个客户端链接到服务端。Tomcat是这么实现的:

三、NIO

  概念:bio的性能是相对较差的,在NIO中,基于块的概念,可以在不编写本地代码的情况下利用底层优化。

  NIO结构图:

来个复杂点的:

selectionKey则是用来描述相关事件。

  1、通道(channel)

2、缓冲区(buffer)

3、选择器(selector)

简单的NIO示例:

服务端:NIOServer

package com.ty.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * 服务端
 */
public class NIOServer {

    /**
     * 定义服务端的selector,
     * 主要作用:
     * 1、将各种事件注册到selector中,selector监控各种事件的发生,例如accept、read等
     * 2、将不同事件分配到不同的channel
     */
    private Selector selector;

    //服务端初始化
    public void init() throws IOException {
        //创建一个selector对象
        this.selector = Selector.open();
        //创建serverSocketChanel对象
        ServerSocketChannel serverSocketChanel = ServerSocketChannel.open();
        //设置为非阻塞
        serverSocketChanel.configureBlocking(false);
        //通过serverSocketChannel对象获取serverSocket
        ServerSocket serverSocket = serverSocketChanel.socket();
        //绑定端口
        InetSocketAddress address = new InetSocketAddress(8080);
        serverSocket.bind(address);
        //注册accept事件到selector中,accept用于获取客户端请求
        serverSocketChanel.register(selector, SelectionKey.OP_ACCEPT);
    }

    //服务端启动服务
    public void start() throws IOException {
        //这地方只做一个最简单的示例,不考虑服务端stop
        while(true) {
            /**
             * selector监控客户端是否有对应事件发生,例如accept、read等等。
             * 注:此方法是阻塞的,当客户端一直没有事件触发,线程一直挂起,直到至少有一事件触发,走后续流程
             */
            selector.select();

            //获取该selector监控到的所有触发的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //拿到迭代器,循环所有监控到的事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()) {
                //事件用SelectionKey描述,主要包括connect、accept、read、write事件
                SelectionKey selectionKey = iterator.next();
                //每种事件只处理一次,避免重复处理
                iterator.remove();
                if(selectionKey.isAcceptable()) {
                    accept(selectionKey);
                }
                if(selectionKey.isReadable()) {
                    read(selectionKey);
                }
            }
        }
    }

    private void accept(SelectionKey selectionKey) throws IOException {
        /**
         * 从selectionkey中获取serverSocketChannel。
         * ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样
         */
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        //serverSocketChannel监听到新连接后,会创建socketChannel。获取socketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //设置为非阻塞
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void read(SelectionKey selectionKey) throws IOException {
        /**
         * SocketChannel是一个连接到TCP网络套接字的通道,就像标准IO中的socket
         * 创建方式:可以通过以下2种方式创建SocketChannel
         * 1、打开一个SocketChannel并连接到互联网上的某台服务器。
         * 2、一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
         */
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        //创建读取缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //通过socketChannel.read()获取客户端的请求数据
        socketChannel.read(byteBuffer);
        String request = new String(byteBuffer.array()).trim();
        System.out.println("客户端发送的请求为:" + request);
        //将一个数组包装成ByteBuffer
        ByteBuffer outBuffer = ByteBuffer.wrap("请求收到啦!".getBytes());
        //数据发送到客户端
        socketChannel.write(outBuffer);
    }

    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.init();
        server.start();
    }
}

客户端:NIOClient

package com.ty.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOClient {

    private Selector selector;

    private BufferedReader clientInput = new BufferedReader(new InputStreamReader(System.in));

    public void init() throws IOException {
        //创建selector
        this.selector = Selector.open();
        //创建SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
        //注册connect事件
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }

    public void start() throws IOException {
        while(true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                if(selectionKey.isConnectable()) {
                    connect(selectionKey);
                }
                if(selectionKey.isReadable()) {
                    read(selectionKey);
                }
            }
        }
    }

    public void connect(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        //如果客户端正在链接
        if(socketChannel.isConnectionPending()) {
            //如果客户端已经链接成功
            if(socketChannel.finishConnect()) {
                socketChannel.configureBlocking(false);
                //链接成功后自然要获取服务端的返回,因此注册read事件
                socketChannel.register(selector, SelectionKey.OP_READ);
                String request = clientInput.readLine();
                //数据发送到服务端
                socketChannel.write(ByteBuffer.wrap(request.getBytes()));
            }else {
                //事件未注册成功,取消掉
                selectionKey.cancel();
            }
        }
    }

    public void read(SelectionKey selectionKey) throws IOException {
        //socketChannel与服务端的对应,双方友好建立一个通道
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        socketChannel.read(byteBuffer);
        System.out.println("服务端响应为:" + new String(byteBuffer.array()).trim());
        String request = clientInput.readLine();
        socketChannel.write(ByteBuffer.wrap(request.getBytes()));
    }

    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.init();
        client.start();
    }
}

测试结果:

服务端:

客户端:

这样通过NIO,客户端与服务端即可正常通信。

Tomcat中的NIO实现:

原文地址:https://www.cnblogs.com/alimayun/p/10639951.html

时间: 2024-12-09 04:31:30

Tomcat架构解析(六)-----BIO、NIO、NIO2、APR的相关文章

tomcat架构解析:Catalina+Coyote+Jasper+配置管理+集群+调优等

在目前流行的互联网架构中,对一个应用来说,Tomcat是首,SSM是中,JVM是尾,我们通常对于SSM是比较了解的,而忽略了首尾,而Tomcat在目前的网络编程中是举足轻重的,但是我们其实对Tomcat中很多原理性的东西不太了解,如果能够掌握Tomcat的原理,那么是非常有用的,比如: 如果我们能弄清楚Tomcat和Socket.Tcp之间的关系,我们就能明白Tomcat为什么会出现端口冲突.如果我们能准确的知道Tomcat中部署一个项目的N种方式,那么就能在工作中更加得心应手.Tomcat中热

终于有人把tomcat讲清楚了!阿里大牛推荐的tomcat架构解析文档

在目前流行的互联网架构中,对一个应用来说,Tomcat是首,SSM是中,JVM是尾,我们通常对于SSM是比较了解的,而忽略了首尾,而Tomcat在目前的网络编程中是举足轻重的,但是我们其实对Tomcat中很多原理性的东西不太了解,如果能够掌握Tomcat的原理,那么是非常有用的,比如: 如果我们能弄清楚Tomcat和Socket.Tcp之间的关系,我们就能明白Tomcat为什么会出现端口冲突.如果我们能准确的知道Tomcat中部署一个项目的N种方式,那么就能在工作中更加得心应手.Tomcat中热

Tomcat架构解析(二)-----Connector、Tomcat启动过程以及Server的创建过程

Connector用于跟客户端建立连接,获取客户端的Socket,交由Container处理.需要解决的问题有监听.协议以及处理器映射等等. 一.Connector设计   Connector要实现的主要功能如下: 设计图如下: 1.ProtocolHandler Connector中的ProtocolHandler用于处理不同的通信协议,Tomcat主要支持HTTP.AJP协议,并且支持BIO.NIO.APR等I/O方式.ProtocolHandler中使用AbstractEndpoint启动

Tomcat 的三种(bio,nio.apr) 高级 Connector 运行模式

tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.或者登录他们的默认页面http://localhost:8080/查看其中的服务器状态. 1)bio 默认的模式,性能非常低下,没有经过任何优化处理和支持. 2)nio 利用java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接修改server.xml里的Connector节点,修改protocol为  <Connector port="80&quo

Tomcat架构解析(四)-----Coyote、HTTP、AJP、HTTP2等协议

Connector是Tomcat中非常重要的一个组成部分,说白了,就是如何从客户端获取到相应的请求信息.这部分主要包括的难点有这样几个部分: 1.客户端与服务端的协议 客户端与服务端的协议是多种多样的,Tomcat肯定不能仅仅支持HTTP协议 2.数据I/O方式 I/O通常有NIO.BIO等多种方式,如何提高数据传输的效率? 一.Coyote   1.Coyote简介 Tomcat中的Connector就是Coyote,功能主要是封装了底层的网络通信.为Catalina容器提供了统一的接口,使容

Tomcat架构解析(三)-----Engine、host、context解析以及web应用加载

上一篇博文介绍了Server的创建,在Server创建完之后,就进入到Engine的创建过程,如下: 一.Engine的创建   1.创建Engine实例 当前次栈顶元素为Service对象,通过Service对象的setContainer()方法,将Engine对象添加到Service中. 2.为Engine添加集群配置 3.为Engine添加生命周期监听器 4.为Engine添加安全配置 二.Host的创建   1.创建Host的实例 addCallMethod(String rule,St

tomcat架构分析 (connector NIO 实现)

出处:http://gearever.iteye.com 上一篇简单记录了缺省配置的connector的内部构造及消息流,同时此connector也是基于BIO的实现.除了BIO外,也可以通过配置快速部署NIO的connector.在server.xml中如下配置: Xml代码 <Connector port="80" URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioPr

tomcat架构分析-索引

tomcat架构分析 (概览) tomcat架构分析 (容器类) tomcat架构分析 (valve机制) tomcat架构分析 (valve源码导读) tomcat架构分析 (Session管理) tomcat架构分析 (JNDI配置) tomcat架构分析 (JNDI体系绑定) tomcat架构分析 (connector BIO 实现) tomcat架构分析 (connector NIO 实现)

(转)tomcat架构分析

        出处:http://gearever.iteye.com tomcat架构分析 (概览) tomcat架构分析 (容器类) tomcat架构分析 (valve机制) tomcat架构分析 (valve源码导读) tomcat架构分析 (Session管理) tomcat架构分析 (JNDI配置) tomcat架构分析 (JNDI体系绑定) tomcat架构分析 (connector BIO 实现) tomcat架构分析 (connector NIO 实现)