一、简介
netty是一个高性能、异步事件驱动的NIO框架,它基于Java Nio提供的API实现,提供了对TCP、UDP和文件传输的支持。
二、Reactor模型
Reactor是一种并发处理客户端请求响应的事件驱动模型。服务端在接收到客户端请求后采用多路复用策略,通过一个非阻塞的线程来异步接收所有的客户端请求。
(1)Reactor单线程模型
一个线程负责建立连接、读、写等操作。如果在业务中处理中出现了耗时操作,就会导致所有请求全部处理延时。
(2)Reactor多线程模型
只是在单线程的模型情况下,使用线程池管理多个线程,用多线程进行处理业务。
(3)Reactor主从多线程模型
该模型采用一个主Reactor仅处理连接,而多个子Reactor用于处理IO读写。然后交给线程池处理业务。Tomcat就是采用该模式实现。效率高
三、Netty的核心组件
(1)BootStrap/ServerBootStrap
BootStrap用于客户端服务启动。ServerBootStrap用于服务端启动。
(2)NioEventLoop
基于线程队列的方式执行事件操作,具体要执行的事件操作包括连接注册、端口绑定和I/O数据读写等。每个NioEventLoop线程都负责多个Channel的事件处理。
(3)NioEventLoopGroup
对NioEventLoop的生命周期进行管理
(4)Future/ChannelFuture
用于异步通信的实现,基于异步通信方式可以在I/O操作触发后注册一个事件,在I/O操作(数据读写完成或失败)完成后自动触发监听事件并完成后续操作。
(5)Channel
是netty中的网络通信组件,用于执行具体的I/O操作。主要功能包括:网络连接的建立、连接状态的管理、网络连接参数的配置、基于异步NIO的网络数据操作。
(6)Selector
用于I/O多路复用中Channel的管理
(7)ChannelHandlerContext
Channel上下文信息的管理。每个ChannelHandler都对应一个ChannelHandlerContext。
(8)ChannelHandler
I/O事件的拦截和处理。
其中ChannelInBoundHandler用于处理数据接收的I/O操作。
其中ChannelOutBoundHandler用于处理数据发送的I/O操作。
(9)ChannelPipeline
基于拦截器设计模式实现的事件拦截处理和转发。
每个Channel都对应一个ChannelPipeline,在ChannelPipeline中维护了一个由ChannelHandlerContext组成的双向链表,每个ChannelHandlerContext都对应一个ChannelHandker,以完成具体Channel事件的拦截和处理。
四、netty的运行原理
Server工作流程:
1、server端启动时绑定本地某个端口,初始化NioServerSocketChannel.
2、将自己NioServerSocketChannel注册到某个BossNioEventLoopGroup的selector上。
server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup
Boss NioEventLoopGroup专门负责接收客户端的连接,Worker NioEventLoopGroup专门负责网络的读写。
NioEventLoopGroup相当于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1个事件循环线程。
BossNioEventLoopGroup循环执行的任务:
- 轮询accept事件;
- 处理accept事件,将生成的NioSocketChannel注册到某一个WorkNioEventLoopGroup的Selector上。
- 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务。
WorkNioEventLoopGroup循环执行的任务:
- 轮询read和Write事件
- 处理IO事件,在NioSocketChannel可读、可写事件发生时,回调(触发)ChannelHandler进行处理。
- 处理任务队列的任务,即 runAllTasks
五、netty怎么解决粘包拆包的
使用解码器解决,解码器一个有4种
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder());
- 固定长度的拆包器 FixedLengthFrameDecoder , 每个应用层数据包的拆分都是固定长度大小
- 行拆包器 LineBasedFrameDecoder ,每个应用层数据包,都以换行符作为分隔符,进行分割拆分
- 分隔符拆包器 DelimiterBasedFrameDecoder ,每个应用层数据包,都通过自定义分隔符,进行分割拆分
- 基于数据包长度的拆包器,LengthFieldBasedFrameDecoder 将应用层数据包的长度,作为接收端应用层数据包的拆分依据。(常用)
六、netty性能为什么这么高
(1)网络模型(I/O多路复用模型)
多路复用IO,采用一个线程处理连接请求,多个线程处理IO请求,他比BIO能处理更多请求,数据请求和数据处理都是异步,底层采用了linux的select、poll、epoll
(2)数据零拷贝
netty的数据接受和发送均采用对外直接内存进行socket读写,堆外内存可以直接操作系统内存,不需要来回的进行字节缓冲区的二次复制。同时netty提供了组合buffer对象,可以避免传统通过内存复制的方式合并buffer时带来的性能损耗。
netty文件传输采用transfeTo方法操作,完全零拷贝。
(3)内存重用机制
使用堆外直接内存,重用缓冲区,不需要jvm回收内存
(4)无锁设计
netty内部采用串行无锁化设计对I/O操作,避免多线程竞争CPU和资源锁定导致的性能下降。
(5)高性能的序列化框架
使用google的protoBuf实现数据的序列化
(6)使用FastThreadLocal类
使用FastThreadLocal类替代ThreadLocal类
七、netty使用
1、引入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.36.Final</version>
</dependency>
2、编写服务端代码(固定写法)
public class Server {
private int port;
public Server(int port) {
this.port = port;
}
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写)
try {
ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置
bootstrap.group(bossGroup, workerGroup) //绑定两个线程组
.channel(NioServerSocketChannel.class) //指定NIO的模式
.childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ServerHandler());
}
})
/**
* 对于ChannelOption.SO_BACKLOG的解释:
* 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
* 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
* 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
* 从B队列中取出完成了三次握手的连接。
* A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
* 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
* 连接数并无影响,backlog影响的只是还没有被accept取出的连接
*/
.option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
.option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
.option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
.childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server(8379).run();
}
}
3、编写Handler处理类
public class ServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//do something msg
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data, "utf-8");
System.out.println("Server: " + request);
//写给客户端
String response = "我是反馈的信息";
ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
//.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}