Netty是什么?
Java可以通过IO和NIO的方式进行网络编程与网络上的其他程序进行通讯。
IO
首先,普通的IO方式,服务端获取到来自网络上的一个连接后,Java线程获取到连接的输入或者输出流进行数据写入或者写出,它是一个阻塞的方式,它必须等到有数据的时候才会继续,否则线程一直阻塞在这里。也就是说,当服务端有大量连接需要处理时,通常我们的数据写入或者写出都受业务控制,大概率出现大量线程等待写入或者写出数据而阻塞,导致服务器程序性能问题,由此可见,传统的IO网络编程方式,对这种性能要求很高的服务器是不满足的。
NIO
Java又提供了NIO的方式进行网络数据的处理,这种方式是非阻塞的数据处理方式,只有当真正有连接,或者有数据可以读或者写时,我们才给它分配线程和其它资源进行业务处理,这就大大提升了资源的利用,从而能够提升服务程序的性能。
但是,NIO的方式也有它的问题:1、编码复杂,要通过原生NIO实现一个可以商用的服务器,需要精通NIO,编写大量的代码,2、JDK NIO中著名的BUG–epoll空轮询,当select返回0时,会导致Selector空轮询而导致CUP100%,官方表示JDK1.6之后修复了这个问题,其实只是发生的概率降低了,没有根本上解决。
Netty
所以,就需要一个框架来简化我们的编程,同时又能保证最低的性能损失。而Netty就是在这些框架中做得最好的。
它具有以下一些特点:
1.API使用简单,更容易上手,开发门槛低
2.功能强大,预置了多种编解码功能,支持多种主流协议
3.定制能力高,可以通过ChannelHandler对通信框架进行灵活地拓展
4.高性能,与目前多种NIO主流框架相比,Netty综合性能最高
5.高稳定性,解决了JDK NIO的BUG
6.经历了大规模的商业应用考验,质量和可靠性都有很好的验证。
Netty架构
首先来看看来源于Doug Lea大神的两张NIO线程模型图
Reactor线程模型![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e3f06901f58d521fbdc8b9ae618c56c4.png)
Reactor - 负责管理IO事件,由图可以看出,它主要有两个职责,一个是处理来自连接事件,由acceptor具体实现;另一个是处理读取和发送数据的事件,由Handler具体实现。
Acceptor - 接受客户端连接,并进行用于数据的读取和写出的Handler的创建。
Handler - 事件处理,用以实现具体的业务逻辑。图中read,decode,compute,encode和send都是由handler实现的。
Thread Pool - Thread Pool中的thread被称作worker thread。Handler中的decode,compute和encode是用worker thread执行的。另外,Handler中的read和send方法是在Reactor线程而不是worker thread中执行的。这意味着对socket数据的读取发送数据和对数据的处理是在不同的线程中进行的,也就是说如果数据很大,则可能会导致reactor线程处理连接的时间边长,影响性能 。
改进后的线程主从reactor线程模型![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/91f098c3050b8af6008f1f9ab4f95196.png)
与普通的Reactor线程模型的不同,这里将原先的处理连接和数据读写的一个Reactor分离成专门用于处理连接的主Reactor和多个用于处理数据读写的从Reactor,将职责分离,可以提升处理连接的性能,各个连接间的数据读写也不会互相受到影响。
Netty核心类
- Bootstrap or ServerBootstrap:我们编写一个Netty程序的入口,这里可以进行整个Netty程序的配置,Netty程序的启动
- EventLoop:事件轮询处理线程。EventLoop相当于特殊的线程,里面包含有与之绑定的selector、executor用于执行线程的线程池等
- EventLoopGroup:事件轮询处理线程管理组,负责配置和管理事件轮询线程。
- ChannelPipeline:通道对应的处理管道,Netty使用了责任链模式,每一个Channel都对应了一个Pipeline,方便进行Channel数据的灵活处理,编解码,业务处理等。
- Channel:代表了一个Socket链接,或者其它和IO操作相关的组件,它和EventLoop一起用来参与IO处理
- Future or ChannelFuture:在Netty中所有的IO操作都是异步的,因此,你不能立刻得知消息是否被正确处理,但是我们可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发。总之,所有的操作都会返回一个ChannelFuture
- ChannelInitializer:方便Channel初始化配置
- ChannelHandler:具体的业务处理Handler
实质上EventLoop就是我们前面说的主从Reactor的抽象,Netty实质就是实现了Doug Lea的主从Reactor线程模型的一个高性能网络编程框架。它还通过一定的方法避免了NIO中的空轮询的BUG。
Netty三大核心概念
Netty线程模型
这个前面其实已经从IO到NIO再到NIO编程模型已经详细阐述了,Netty的线程模型就是上面改进过的Reactor多线程模型。
责任链模式在Netty中的应用
责任链模式几大要素
1、handler(具体的处理单元)
2、链(可以用list存储,也可以是链,由handler构成)
3、上下文(管理链,负责保存链和进行处理传播等工作)
netty中的责任链
netty中的pipeline,一切均通过pipeline开始处理,从NioEventLoop的初始化、channel的初始化,端口的绑定、到网络连接事件、数据读取,一切皆事件,事件传播通过责任链传播,责任链上的handler处理它自己需要处理的,Netty提供的Handler抽象分为Inbound和Outbound两种,如下图一个责任链的结构。
高效的ByteBuff
可以使用两种方式对ByteBuf进行分类:按底层实现方式和按是否使用对象池。
按底层实现
HeapByteBuf
HeapByteBuf的底层实现为JAVA堆内的字节数组。堆缓冲区与普通堆对象类似,位于JVM堆内存区,可由GC回收,其申请和释放效率较高。常规JAVA程序使用建议使用该缓冲区。
DirectByteBuf
DirectByteBuf的底层实现为操作系统内核空间的字节数组。直接缓冲区的字节数组位于JVM堆外的NATIVE堆,由操作系统管理申请和释放,而DirectByteBuf的引用由JVM管理。直接缓冲区由操作系统管理,一方面,申请和释放效率都低于堆缓冲区,另一方面,却可以大大提高IO效率。由于进行IO操作时,常规下用户空间的数据(JAVA即堆缓冲区)需要拷贝到内核空间(直接缓冲区),然后内核空间写到网络SOCKET或者文件中。如果在用户空间取得直接缓冲区,可直接向内核空间写数据,减少了一次拷贝,可大大提高IO效率,这也是常说的零拷贝。
CompositeByteBuf
CompositeByteBuf,顾名思义,有以上两种方式组合实现。这也是一种零拷贝技术,想象将两个缓冲区合并为一个的场景,一般情况下,需要将后一个缓冲区的数据拷贝到前一个缓冲区;而使用组合缓冲区则可以直接保存两个缓冲区,因为其内部实现组合两个缓冲区并保证用户如同操作一个普通缓冲区一样操作该组合缓冲区,从而减少拷贝操作。
按是否使用对象池
UnpooledByteBuf
UnpooledByteBuf为不使用对象池的缓冲区,不需要创建大量缓冲区对象时建议使用该类缓冲区。
PooledByteBuf
PooledByteBuf为对象池缓冲区,当对象释放后会归还给对象池,所以可循环使用。当需要大量且频繁创建缓冲区时,建议使用该类缓冲区。Netty4.1默认使用对象池缓冲区,4.0默认使用非对象池缓冲区。
使用Netty构建Helloworld服务器
直接上代码,一个简单的应答服务
package com.study;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.ReferenceCountUtil;
public class NettyTest
{
public static void main(String[] args) throws InterruptedException
{
EventLoopGroup parent = new NioEventLoopGroup();
EventLoopGroup child = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parent, child).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.writeAndFlush(msg);
}
@Override
@SuppressWarnings("deprecation")
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.out.println("error");
cause.printStackTrace();
ctx.close();
}
});
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
bootstrap.bind(new InetSocketAddress(8080)).sync();
System.out.println("server started");
}
}