Netty是一款高效的NIO框架和工具,基于JAVA NIO提供的API实现。

在JAVA NIO方面Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式设计了高效的线程模型,Reactor模式的参与者主要有下面一些组件:

  

  1. Channel

  2. Selector
  3. EventLoopGroup/EventLoop

  4. ChannelPipeline

Channel

在网络IO方面,Channel的主要实现是ServerSocketChannel和SocketChannel。他们都代表一个面向流的可监听读写事件的socket。ServerSocketChannel是用于服务器端的socket,他提供了一个静态工具方法open来为用户提供获取Channel的工具:

public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}

ServerSocketChannel的使用方式是面向服务器端的,一般的开发流程是:

  1. 获取一个ServerSocketChannel。

  2. 设置网络操作,这些参数主要是和TCP协议有关。

  3. 将ServerSocketChannel注册到Selector(多路复用器)。

  4. 将ServerSocketChannel和某个具体的地址绑定。

  5. 用户像多路复用器设置感兴趣的IO事件。

  6. 用户线程以阻塞或非阻塞方式轮询Selector来查看是否有就绪的IO事件。

  7. 用户针对不同的IO事件对Channel进行具体的IO操作。

SocketChannel主要是面向客户端的开发的,也是以open方式获取channel,客户端的开发流程大致如下:

  1. 获取一个SocketChannel。

  2. 设置Channel为非阻塞方式。

  3. 获取Selector。

  4. 将channel注册到Selector,并监听CONNECT事件。

  5. 调用channel的connect方法连接指定的服务器和端口。

  6. 如果连接成功则进行IO操作,如果没成功则轮询Selector处理CONNECT事件。

Selector

Selector是JAVA NIO提供的SelectableChannel多路复用器,它内部维护着三个SelectionKey集合,负责配合select操作将就绪的IO事件分离出来,落地为SelectionKey,我前面有一篇文章的一部分对Selector进行了相对详细的介绍(这里)。

在Netty线程模型中,我认为Selector充当着demultiplexer的角色,而对于SelectionKey我们可以将它看成Reactor模式中的资源。

EventLoopGroup/EventLoop

EventLoopGroup是一组EventLoop的抽象,由于Netty对Reactor模式进行了变种,实际上为更好的利用多核CPU资源,Netty实例中一般会有多个EventLoop同时工作,每个EventLoop维护着一个Selector实例,类似单线程Reactor模式地工作着。

EventLoopGroup提供next接口,可以总一组EventLoop里面按照一定规则获取其中一个EventLoop来处理任务.

1. BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector实例,

2. BoosEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,

3. 然后将accept得到的SocketChannel(注意不是ServerSocketChannel)交给WorkerEventLoopGroup,

4. WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来将这个SocketChannel注册到其维护的Selector并对其后续的IO事件(OP_READ, OP_WRITE等)进行处理。

在Reactor模式中BossEventLoopGroup主要是对多线程的扩展,而每个EventLoop的实现涵盖IO事件的分离,和分发(Dispatcher)。

定时任务和一般任务

在Netty的EventLoop线程中,这个线程主要需要处理IO事件和其他两种任务,分别为定时任务和一般任务。Netty提供可一个参数ioRatio用于用户调整单线程对于IO处理时间和任务处理时间的分配的比率。这样根据实际应用场景用户可以对这个值进行调整,默认值是50,也就是这个线程会将处理IO的时间和处理任务的时间控制为1:1。

final long ioStartTime = System.nanoTime();

processSelectedKeys();//处理IO事件

final long ioTime = System.nanoTime() - ioStartTime;//处理IO事件的时间
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);//计算用于处理任务的时间

  

这样尽管一个EventLoop会关联多个Channel,这些Channel在单个线程下并不会出现并发问题, 而是串行的顺序执行,同时对于异步任务的处理也一样,Netty这样设计即免去了并发问题的烦恼,有减少了多线程上下文切换带来的性能损耗,同时基于EventLoopGroup实现的有限的线程数能够充分利用CPU处理能力。

ChannelPipeline

在Netty中ChannelPipeline维护着一个ChannelHandler的链表队列,每个SocketChannel都有一个维护着一个ChannelPipeline实例,而每个ChannelPipeline实例通常维护着一个ChannelHandler链表队列,由于SocketChannel是和SelectionKey关联的,也就是Reactor模式中的资源,当EventLoop将SelectionKey分离出来的时候会将SelectionKey关联的Channel交给Channel关联的ChannelHandler链来处理,那么ChannelPipeline其实是担任着Reactor模式中的请求处理器这个角色。

ChannelPipeline的默认实现是DefaultChannelPipeline,DefaultChannelPipeline本身维护着一个用户不可见的tail和head的ChannelHandler,他们分别位于链表队列的头部和尾部。tail在更上从的部分,而head在靠近网络层的方向。

在Netty中关于ChannelHandler有两个重要的接口,ChannelInBoundHandler和ChannelOutBoundHandler。inbound可以理解为网络数据从外部流向系统内部,而outbound可以理解为网络数据从系统内部流向系统外部。用户实现的ChannelHandler可以根据需要实现其中一个或多个接口,将其放入Pipeline中的链表队列中,ChannelPipeline会根据不同的IO事件类型来找到相应的Handler来处理,同时链表队列是责任链模式的一种变种,自上而下或自下而上所有满足事件关联的Handler都会对事件进行处理.

其他扩展

select、poll和epoll

JAVA对NIO的支持是从1.4版本开始的,是基于多路复用技术,而在linux操作系统方面多路复用技术有三种常用的机制:select、poll和epoll,epoll的支持也只是linux2.6版本之后才提供,java在jdk5.0的update 9之后才对epoll进行支持。这三种机制本质上都是同步IO,主要是由于他们都需要在读写事件就绪的时候需要自己进行读写,也就是这个这个读写过程是阻塞的。下面对着三种机制进行简单总结:

  1. select函数:改函数允许进程指示内核等待多个事件中的任何一个发生的时候或者在一定时间之后被唤醒,select有个致命的缺点即在多路复用中文件描述符的数量有限制,如果需要突破限制需要重新编译操作系统内核。

  2. poll函数:poll机制与select机制类似,区别是poll没有最大描述符限制。

  3. epoll函数:epoll在linux2.6内核中被提出来,是之前的select和poll的增强版本。epoll也没有文件描述符数量限制,而且是用一个文件描述符来管理多个描述符。在性能上相比上面两种有了很大的优化。

    epoll原理图如下:

  4. epoll使用“事件”的方式通知用户程序数据就绪,并且使用内存拷贝的方式使用户程序直接读取内核准备好的数据,不用再读取数据

ByteBuffer

JAVA NIO直接和Channel打交道的Buffer是ByteBuffer,ByteBuffer接口提供主要的内存分配、IO读写等相关接口。

值得注意的是JAVA NIO提供了两种Buffer内存分配机制,一种是堆内存,另一种是直接内存,主要区别:

  1. 堆内存分配和回收比较快,但是网络数据需要从内核copy到堆中。

  2. 直接内存分配和回收比较慢,但是免去了从内核copy到堆中的一次copy。

这两种内存各有千秋,使用的时候要根据实际情况去选择。

参考: https://my.oschina.net/andylucc/blog/614295

Netty 核心组件笔记的更多相关文章

  1. Netty学习笔记(二)——netty组件及其用法

    1.Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 原生NIO存在的问题 1) NIO的类库和API繁杂,使用麻烦:需要熟练掌握Selector.Se ...

  2. Netty学习笔记(二) 实现服务端和客户端

    在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...

  3. Netty 学习笔记(1)通信原理

    前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始.   Netty 的通信原理 Netty 底层 ...

  4. Netty 核心组件 Pipeline 源码分析(二)一个请求的 pipeline 之旅

    目录大纲: 前言 针对 Netty 例子源码做了哪些修改? 看 pipeline 是如何将数据送到自定义 handler 的 看 pipeline 是如何将数据从自定义 handler 送出的 总结 ...

  5. Netty核心组件介绍及手写简易版Tomcat

    Netty是什么: 异步事件驱动框架,用于快速开发高i性能服务端和客户端 封装了JDK底层BIO和NIO模型,提供高度可用的API 自带编码解码器解决拆包粘包问题,用户只用关心业务逻辑 精心设计的Re ...

  6. Netty学习笔记-入门版

    目录 Netty学习笔记 前言 什么是Netty IO基础 概念说明 IO简单介绍 用户空间与内核空间 进程(Process) 线程(thread) 程序和进程 进程切换 进程阻塞 文件描述符 文件句 ...

  7. 【Netty】Netty核心组件介绍

    一.前言 前篇博文体验了Netty的第一个示例,下面接着学习Netty的组件和其设计. 二.核心组件 2.1. Channel.EventLoop和ChannelFuture Netty中的核心组件包 ...

  8. Netty学习笔记(六) 简单的聊天室功能之WebSocket客户端开发实例

    在之前的Netty相关学习笔记中,学习了如何去实现聊天室的服务段,这里我们来实现聊天室的客户端,聊天室的客户端使用的是Html5和WebSocket实现,下面我们继续学习. 创建客户端 接着第五个笔记 ...

  9. Netty 核心组件 EventLoop 源码解析

    前言 在前文 Netty 启动过程源码分析 (本文超长慎读)(基于4.1.23) 中,我们分析了整个服务器端的启动过程.在那篇文章中,我们重点关注了启动过程,而在启动过程中对核心组件并没有进行详细介绍 ...

随机推荐

  1. 多线程:Operation(一)

    1. 进程和线程 1.1 进程 进程:正在运行的应用程序叫进程 进程之间都是独立的,运行在专用且受保护的内存空间中 两个进程之间无法通讯 通俗的理解,手机上同时开启了两个App.这两个App肯定是在不 ...

  2. 使用Oozie中workflow的定时任务重跑hive数仓表的历史分期调度

    在数仓和BI系统的开发和使用过程中会经常出现需要重跑数仓中某些或一段时间内的分区数据,原因可能是:1.数据统计和计算逻辑/口径调整,2.发现之前的埋点数据收集出现错误或者埋点出现错误,3.业务数据库出 ...

  3. JDK8的新特性

  4. flask 文件上传(单文件上传、多文件上传)

    文件上传 在HTML中,渲染一个文件上传字段只需要将<input>标签的type属性设为file,即<input type=”file”>. 这会在浏览器中渲染成一个文件上传字 ...

  5. 转:【专题四】自定义Web浏览器

    前言: 前一个专题介绍了自定义的Web服务器,然而向Web服务器发出请求的正是本专题要介绍的Web浏览器,本专题通过简单自定义一个Web浏览器来简单介绍浏览器的工作原理,以及帮助一些初学者揭开浏览器这 ...

  6. HDU 1846 Brave Game (巴什博弈)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1846 十年前读大学的时候,中国每年都要从国外引进一些电影大片,其中有一部电影就叫<勇敢者的游戏& ...

  7. bind的封装

    1.bind.call.apply三者的区别: 1)bind的返回值是一个函数体,不会被立即调用 2)call.apply会立即调用,第一个参数都是用来改变this的指向,两者的区别是前者传递参数的时 ...

  8. reids非关系性数据库

     1.Redis环境配置 下载安装地址: https://github.com/MicrosoftArchive/redis/releases 解压文件到指定的目录,D:\ChromeCoreDown ...

  9. Docker学习笔记之Docker的数据管理和存储

    0x00 概述 数据是应用程序重要的产出,所以很好的管理和存储数据,是对应用程序劳动结果的尊重.特别是在大数据时代,所有的数据都是重要的资产,保护好数据是每个开发者必须掌握的技能.我们知道,在 Doc ...

  10. Git clone 报错 Unable to negotiate with xxx.xxx.xxx.xxx port 12345: no matching cipher found. Their offer: aes128-cbc,3des-cbc,blowfish-cbc

    git clone 报错 Unable to negotiate with xxx.xxx.xxx.xxx. port 12345: no matching cipher found. Their o ...