IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Jetty架构特点之Connector组件 -> 正文阅读

[Java知识库]Jetty架构特点之Connector组件

Jetty是Eclipse基金会的一个开源项目,和Tomcat一样,Jetty也是一个“HTTP服务器 + Servlet容器”,并且Jetty和Tomcat在架构设计上有不少相似的地方。但同时Jetty也有自己的特点,主要是更加小巧,更易于定制化。Jetty作为一名后起之秀,应用范围也越来越广,比如Google App Engine就采用了Jetty来作为Web容器。

Jetty整体架构

Jetty Server:多个Connector(连接器)、多个Handler(处理器),以及一个线程池

Jetty中的Connector组件和Handler组件分别来实现HTTP服务器和Servlet容器的功能,这两个组件工作时所需要的线程资源都直接从一个全局线程池ThreadPool中获取。

Jetty Server可以有多个Connector在不同的端口上监听客户请求,而对于请求处理的Handler组件,也可以根据具体场景使用不同的Handler。这样的设计提高了Jetty的灵活性,需要支持Servlet,则可以使用ServletHandler;需要支持Session,则再增加一个SessionHandler。也就是说我们可以不使用Servlet或者Session,只要不配置这个Handler就行了。

一个Socket上可以接收多个HTTP请求,每次请求跟一个Hanlder线程是一对一的关系,因为keepalive,一次请求处理完成后Socket不会立即关闭,下一次再来请求,会分配一个新的Hanlder线程。

如果有很多TCP建立连接后迟迟没有写入数据导致连接请求堵塞,或
如果有很多handle在处理耗时I/O操作,同样可能拖慢整个线程池,进而影响到accepters和selectors,可能拖慢整个线程池,jetty如何应对呢?
这就是为什么Servlet3.0中引入了异步Servlet的概念,就是说遇到耗时的I/O操作,Tomcat的线程会立即返回,当业务线程处理完后,再调用Tomcat的线程将响应发回给浏览器。

为了启动和协调上面的核心组件工作,Jetty提供了一个Server类来做这个事情,它负责创建并初始化Connector、Handler、ThreadPool组件,然后调用start方法启动它们。

对比Tomcat架构

Tomcat在整体上跟Jetty相似,但是:

  • Jetty中没有Service概念
  • Tomcat中的Service包装了多个连接器和一个容器组件,一个Tomcat实例可以配置多个Service,不同Service通过不同的连接器监听不同的端口;而Jetty中Connector是被所有Handler共享的。

Tomcat中每个连接器都有自己的线程池,而在Jetty中所有的Connector共享一个全局的线程池。

Connector组件

跟Tomcat一样,Connector的主要功能是对I/O模型和应用层协议的封装。I/O模型方面,最新的Jetty 9版本只支持NIO,因此Jetty的Connector设计有明显的Java NIO通信模型的痕迹。至于应用层协议方面,跟Tomcat的Processor一样,Jetty抽象出了Connection组件来封装应用层协议的差异。

Java NIO

Java NIO的核心组件是Channel、Buffer和Selector。
Channel表示一个连接,可以理解为一个Socket,通过它可以读取和写入数据,但是并不能直接操作数据,需要通过Buffer来中转。

Selector可以用来检测Channel上的I/O事件,比如读就绪、写就绪、连接就绪,一个Selector可以同时处理多个Channel,因此单个线程可以监听多个Channel,这样会大量减少线程上下文切换的开销。

同一个浏览器发过来的请求会重用TCP连接,也就是用同一个Channel。Channel是非阻塞的,连接器里维护了这些Channel实例,过了一段时间超时到了channel还没数据到来,表面用户长时间没有操作浏览器,这时Tomcat才关闭这个channel。

服务端NIO程序

  1. 创建服务端Channel,绑定监听端口并把Channel设为非阻塞方式。
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
server.configureBlocking(false);
  1. 创建Selector,并在Selector中注册Channel感兴趣的事件OP_ACCEPT,告诉Selector如果客户端有新的连接请求到这个端口就通知我。
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
  1. Selector会在一个死循环里不断调用select去查询I/O状态,查询某个Channel上是否有数据可读。select会返回一个SelectionKey列表,Selector会遍历这个列表,看看是否有“客户”感兴趣的事件,如果有,就采取相应的动作。

比如下面这个例子,如果有新的连接请求,就会建立一个新的连接。连接建立后,再注册Channel的可读事件到Selector中,告诉Selector我对这个Channel上是否有新的数据到达感兴趣。

 while (true) {
        selector.select();//查询I/O事件
        for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) { 
            SelectionKey key = i.next(); 
            i.remove(); 

            if (key.isAcceptable()) { 
                // 建立一个新连接 
                SocketChannel client = server.accept(); 
                client.configureBlocking(false); 
                
                //连接建立后,告诉Selector,我现在对I/O可读事件感兴趣
                client.register(selector, SelectionKey.OP_READ);
            } 
        }
    } 

所以服务端在I/O通信上主要完成了三件事情:

  • 监听连接
  • I/O事件查询
  • 数据读写

Jetty设计了Acceptor、SelectorManager和Connection来分别做这三事。

Acceptor

独立的Acceptor线程组处理连接请求。多个Acceptor共享同一个ServerSocketChannel。多个Acceptor线程调用同一个ServerSocketChannel#accept,由操作系统保证线程安全。

为啥Acceptor组件是直接使用 ServerSocketChannel.accept() 接受连接的,为什么不使用向Selector注册OP_ACCEPT事件的方式来接受连接?直接调用.accept()方法有什么考虑?
直接调用accept方法,编程上简单一些,否则每个Acceptor又要自己维护一个Selector。

在Connector的实现类ServerConnector中,有一个_acceptors的数组,在Connector启动的时候, 会根据_acceptors数组的长度创建对应数量的Acceptor,而Acceptor的个数可以配置。

for (int i = 0; i < _acceptors.length; i++)
{
  Acceptor a = new Acceptor(i);
  getExecutor().execute(a);
}

Acceptor是ServerConnector中的一个内部类,同时也是一个Runnable,Acceptor线程是通过getExecutor得到的线程池来执行的,前面提到这是一个全局的线程池。

Acceptor通过阻塞方式接受连接。

public void accept(int acceptorID) throws IOException
{
  ServerSocketChannel serverChannel = _acceptChannel;
  if (serverChannel != null && serverChannel.isOpen())
  {
    // 这里是阻塞的
    SocketChannel channel = serverChannel.accept();
    // 执行到这里时说明有请求进来了
    accepted(channel);
  }
}

接受连接成功后会调用accepted,将SocketChannel设置为非阻塞模式,然后交给Selector去处理。

private void accepted(SocketChannel channel) throws IOException
{
    channel.configureBlocking(false);
    Socket socket = channel.socket();
    configure(socket);
    // _manager是SelectorManager实例,里面管理了所有的Selector实例
    _manager.accept(channel);
}

SelectorManager

Jetty的Selector由SelectorManager类管理,而被管理的Selector叫作ManagedSelector。SelectorManager内部有一个ManagedSelector,真正的打工人。

每个ManagedSelector都有自己的Selector,多个Selector可以并行管理大量的channel,提高并发,连接请求到达时采用Round Robin的方式选择ManagedSelector。

public void accept(SelectableChannel channel, Object attachment)
{
  //选择一个ManagedSelector来处理Channel
  final ManagedSelector selector = chooseSelector();
  //提交一个任务Accept给ManagedSelector
  selector.submit(selector.new Accept(channel, attachment));
}

SelectorManager从本身的Selector数组中选择一个Selector来处理这个Channel,并创建一个任务Accept交给ManagedSelector,ManagedSelector在处理这个任务主要做了两步:

  1. 调用Selector#register把Channel注册到Selector上,拿到一个SelectionKey
 _key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);
  1. ,创建一个EndPoint和Connection,并跟这个SelectionKey(Channel)绑定
private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
{
    //1. 创建EndPoint
    EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
    
    //2. 创建Connection
    Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
    
    //3. 把EndPoint、Connection和SelectionKey绑在一起
    endPoint.setConnection(connection);
    selectionKey.attach(endPoint);
    
}

就好像到餐厅吃饭:

  • 先点菜(注册I/O事件)
  • 服务员(ManagedSelector)给你一个单子(SelectionKey)
  • 等菜做好了(I/O事件到了)
  • 服务员根据单子就知道是哪桌点了这个菜,于是喊一嗓子某某桌的菜做好了(调用了绑定在SelectionKey上的EndPoint的方法)

ManagedSelector并没有调用直接EndPoint的方法去处理数据,而是通过调用EndPoint的方法返回一个Runnable,然后把这个Runnable扔给线程池执行,这个Runnable才会去真正读数据和处理请求。

Connection

这个Runnable是EndPoint的一个内部类,它会调用Connection的回调方法来处理请求。Jetty的Connection组件类比就是Tomcat的Processor,负责具体协议的解析,得到Request对象,并调用Handler容器进行处理。下面我简单介绍一下它的具体实现类HttpConnection对请求和响应的处理过程。

请求处理:HttpConnection并不会主动向EndPoint读取数据,而是向在EndPoint中注册一堆回调方法:

getEndPoint().fillInterested(_readCallback);

告诉EndPoint,数据到了你就调我这些回调方法_readCallback吧,有点异步I/O的感觉,也就是说Jetty在应用层面模拟了异步I/O模型。

回调方法_readCallback里,会调用EndPoint的接口去读数据,读完后让HTTP解析器去解析字节流,HTTP解析器会将解析后的数据,包括请求行、请求头相关信息存到Request对象。

响应处理:Connection调用Handler进行业务处理,Handler会通过Response对象来操作响应流,向流里面写入数据,HttpConnection再通过EndPoint把数据写到Channel,这样一次响应就完成了。

  • Connector的工作流

    1.Acceptor监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个Channel,Acceptor将Channel交给ManagedSelector来处理
    2.ManagedSelector把Channel注册到Selector上,并创建一个EndPoint和Connection跟这个Channel绑定,接着就不断地检测I/O事件
    3.I/O事件到了就调用EndPoint的方法拿到一个Runnable,并扔给线程池执行
    4.线程池中调度某个线程执行Runnable
    5.Runnable执行时,调用回调函数,这个回调函数是Connection注册到EndPoint中的
    6.回调函数内部实现,其实就是调用EndPoint的接口方法来读数据
    7.Connection解析读到的数据,生成请求对象并交给Handler组件去处理

总结

Jetty的Connector只支持NIO模型,跟Tomcat的NioEndpoint组件一样,它也是通过Java的NIO API实现的。

在线程模型设计上Tomcat的NioEndpoint跟Jetty的Connector是相似的,都是用一个Acceptor数组监听连接,用一个Selector数组侦测I/O事件,用一个线程池执行请求。它们的不同点在于,Jetty使用了一个全局的线程池,所有的线程资源都是从线程池来分配。
Jetty Connector使用回调函数模拟异步I/O,比如Connection向EndPoint注册了一堆回调函数。它的本质将函数当作一个参数来传递,告诉对方,你准备好了就调这个回调函数。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:31:22  更:2021-07-25 11:31:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 8:38:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码