该文章是Netty相关文章。目的是让读者可以快速的了解netty的相关知识以及开发方法。所以本文章在正式介绍Netty开发前先介绍了Netty的前置相关内容:线程模型,JavaNIO,零拷贝等。本文章以大纲框架的形式总体介绍了Netty,但愿对读者有些帮助。文中图片多来自于百度网络,若是有侵权,能够联系我进行删除。内容如有不当欢迎在评论区指出。
netty是由JBOSS提供的一个Java开源框架,是一个异步的,基于事件驱动的网络应用框架,用以快速开发高性能,高可靠性的网络IO程序.java
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程能够开始 I/O。
Channel:是双向的,既能够用来进行读操做,又能够用来进行写操做bootstrap
Buffer:它经过几个变量来保存这个数据的当前位置状态:设计模式
向Buffer中写数据:api
从Buffer中读取数据:数组
Buffer经常使用方法缓存
Selector:Selector一块儿使用时,Channel必须处于非阻塞模式下。经过channel.register,将channel登记到Selector上,同时添加关注的事件(SelectionKey),经常使用方法以下:安全
缺点:服务器
1. 单进程所打开的FD是具备必定限制的, 2. 套接字比较多的时候,每次select()都要经过遍历Socket来完成调度,无论哪一个Socket是活跃的,都遍历一遍。这会浪费不少CPU时间 3. 每次都须要把fd集合从⽤用户态拷贝到内核态,这个开销在fd不少时会很⼤大
poll:本质上和select没有区别,fd使用链表实现,没有最大链接数的限制。网络
缺点:多线程
epoll:
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。
**epoll底层原理**:调用epoll_create后,内核cache里建了个红黑树用于存储之后epoll_ctl传来的socket,创建一个rdllist双向链表,用于存储准备就绪的事件。在epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据便可。有数据就返回,没有数据就阻塞。
对一个操做系统进程来讲,它既有内核空间(与其余进程共享),也有用户空间(进程私有),它们都是处于虚拟地址空间中。进程没法直接操做I/O设备,必须经过操做系统调用请求内核来协助完成I/O动做。将静态文件展现给用户须要先将静态内容从磁盘中拷贝出来放到内存buf中,而后再将这个buf经过socket发给用户
问题:经历了4次copy过程,4次内核切换
1. 用户态到内核态:调用read,文件copy到内核态内存 2. 内核态到用户态:内核态内存数据copy到用户态内存 3. 用户态到内核态:调用writer:用户态内存数据到内核态socket的buffer内存中 4. 最后内核模式下的socket模式下的buffer数据copy到网卡设备中传送 5. 从内核态回到用户态执行下一个循环
Linux:零拷贝技术消除传输数据在存储器之间没必要要的中间拷贝次数,减小了用户进程地址空间和内核地址空间之间由于上下文切换而带来的开销。
常见零拷贝技术
零拷贝不只仅带来更少的数据复制,还能带来其余的性能优点,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。
Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述问题。
Reactor模式:是事件驱动的,多个并发输入源。它有一个服务处理器,有多个请求处理器;这个服务处理器会同步的将输入的客户端请求事件多路复用的分发给相应的请求处理器。
单Reactor单线程:多路复用、事件分发和消息的处理都是在一个Reactor线程上完成。
* 优势: * 模型简单,实现方便 * 缺点: * 性能差:单线程没法发挥多核性能, * 可靠性差:线程意外终止或死循环,则整个模块不可用
单Reactor多线程
一个Reactor线程负责监听服务端的链接请求和接收客户端的TCP读写请求;NIO线程池负责消息的读取、解码、编码和发送
优势:能够充分的利用多核cpu的处理能 缺点:Reactor处理全部事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈.
主从 Reactor 多线程
MainReactor负责监听服务端的链接请求,接收到客户端的链接后,将SocketChannel从MainReactor上移除,从新注册到SubReactor线程池的线程上。SubReactor处理I/O的读写操做,NIO线程池负责消息的读取、解码、编码和发送。
NioEventLoopGroup:主要管理 eventLoop 的生命周期,能够理解为一个线程池,内部维护了一组线程,每一个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程
ChannelHandler用于处理Channel对应的事件
示例代码:
public class NettyServer { public static void main(String[] args) throws Exception { //bossGroup和workerGroup分别对应mainReactor和subReactor NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workGroup) //用来指定一个Channel工厂,mainReactor用来包装SocketChannel. .channel(NioServerSocketChannel.class) //用于指定TCP相关的参数以及一些Netty自定义的参数 .option(ChannelOption.SO_BACKLOG, 100) //childHandler()用于指定subReactor中的处理器,相似的,handler()用于指定mainReactor的处理器 .childHandler(new ChannelInitializer<SocketChannel>() { //ChannelInitializer,它是一个特殊的Handler,功能是初始化多个Handler。完成初始化工做后,netty会将ChannelInitializer从Handler链上删除。 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //addLast(Handler)方法中不指定线程池那么将使用默认的subReacor即woker线程池执行处理器中的业务逻辑代码。 pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new MyServerHandler()); } }); //sync() 同步阻塞直到bind成功 ChannelFuture f = bootstrap.bind(8888).sync(); //sync()同步阻塞直到netty工做结束 f.channel().closeFuture().sync(); } }
NioEventLoopGroup:
NioEventLoop
NioEventLoop 肩负着两种任务:
ServerBootstrap是一个工具类,用来配置netty
channel():提供一个ChannelFactory来建立channel,不一样协议的链接有不一样的 Channel 类型与之对应,常见的Channel类型:
ChannelHandler下主要是两个子接口
ChannelInboundHandler(入站): 处理输入数据和Channel状态类型改变。
ChannelOutboundHandler(出站): 处理输出数据
ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操做,一个贯穿 Netty 的链。每一个新的通道Channel,Netty都会建立一个新的ChannelPipeline,并将器pipeline附加到channel中。DefaultChinnelPipeline它的Handel头部和尾部的Handel是固定的,咱们所添加的Handel是添加在这个头和尾以前的Handel。
ChannelHandlerContext:ChannelPipeline并非直接管理ChannelHandler,而是经过ChannelHandlerContext来间接管理。
网络中都是以字节码的数据形式来传输数据的,服务器编码数据后发送到客户端,客户端须要对数据进行解码
Netty提供了一些默认的编码器:
StringEncoder:对字符串数据进行编码
ObjectEncoder:对 Java 对象进行编码
StringDecoder:对字符串数据进行解码
ObjectDecoder:对 Java 对象进行解码
抽象解码器
ReplayingDecoder: 继承ByteToMessageDecoder,不须要检查缓冲区是否有足够的字节,可是ReplayingDecoder速度略慢于ByteToMessageDecoder,同时不是全部的ByteBuf都支持。
UDP是基于帧的,包的首部有数据报文的长度.TCP是基于字节流,没有边界的。TCP的首部没有表示数据长度的字段。
发生TCP粘包或拆包的缘由:
Netty 已经提供了编码器用于解决粘包。
Netty彻底工做在用户态的,Netty的零拷贝更多的对数据操做的优化。
Netty的零拷贝(或者说ByteBuf的复用)主要体如今如下几个方面: