大白话聊聊Netty
阿里妹导读
Netty是什么
Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers & clients.
为什么选Netty
开发门槛低:API 使用简单;
定制能力强:可以通过 ChannelHandler对通信框架进行灵活地扩展;
Handler强大:预置了多种编解码器,支持多种主流协议,对传输中粘包和拆包有现成解决方案,有完善的断连,idle(心跳检测)等异常处理;
高性能:与其他业界主流的 NIO 框架对比,Netty 的综合性能最优;
社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时更多的新功能会加入;经历了大规模的商业应用考验,质量有验证;
开发门槛低
BIO vs NIO vs AIO
public class Server {
//定义一个循环接收客户端的Socket连接请求。初始化一个线程池对象
private static ExecutorService poolHandler = new ThreadPoolExecutor(5, 5,
120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
public static void main(String[] args) {
try {
// 注册端口
ServerSocket ss = new ServerSocket(9999);
while (true) {
Socket socket = ss.accept();
// 把Socket封装成一个任务对象交给线程池处理
Runnable target = new ServerRunnable(socket);
poolHandler.execute(target);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static class ServerRunnable implements Runnable {
private Socket socket;
public ServerRunnable(Socket socket) {
this.socket = socket;
}
public void run() {
// 处理接收的客户端Socket通信需求
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {
//处理数据的粘包拆包
//对完整的数据包进行解码操作
//得到客户端消息
//触发各种统计类事件如心跳检测 信息统计
//处理客户端的消息
//得到响应消息
//对响应消息进行编码
}
} catch (IOException e) {
e.printStackTrace();
//处理网络断开事件
//处理其他异常事件
}
}
}
}
NIOServer
public class Server {
public static void main(String[] args) throws IOException {
//获取ServerSocketChannel
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//设置非阻塞模式
ssChannel.configureBlocking(false);
//绑定端口
ssChannel.bind(new InetSocketAddress(9999));
//获取选择器
Selector selector = Selector.open();
//将ServerSocketChannel注册到选择器上,并且监听建立连接事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 使用Selector选择器轮询已经就绪好的事件
while (selector.select() > 0) {
// 获取选择器就绪事件
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
//遍历事件
while (it.hasNext()) {
SelectionKey sk = it.next();
//判断事件类型
if (sk.isAcceptable()) {
// 获取客户端channel
SocketChannel channel = ssChannel.accept();
//切换非阻塞模式
channel.configureBlocking(false);
//将该channel注册到选择器上
channel.register(selector, SelectionKey.OP_READ);
} else if (sk.isReadable()) {
//获取channel
SocketChannel sChannel = (SocketChannel) sk.channel();
//读取网络数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buf)) > 0) {
//处理数据的粘包拆包
//对完整的数据包进行解码操作
//得到客户端消息
//触发各种统计类事件如心跳检测 信息统计
}
}else if(sk.isWritable()){
//得到响应消息
//对响应消息进行编码
}
// 15.取消选择键SelectionKey
it.remove();
}
}
}
}
用Netty进行网络编程时代码是这样的:
public class NettyServer {
public static void main(String[] args) throws Exception{
//设置接受网络连接线程池
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
//设置处理网络除连接外所有事件线程的线程池
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)//设置Channel类型
.option(ChannelOption.SO_BACKLOG,1024)
.handler(new ChannelInitializer<ServerSocketChannel>() {
protected void initChannel(ServerSocketChannel ch) throws Exception {
//设置处理网络连接的Handler
ch.pipeline().addLast("serverBindHandler",
new NettyBindHandler(NettyTcpServer.this,serverStreamLifecycleListeners));
}
})
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline()
.addLast("protocolHandler", new NettyProtocolHandler())//设置编解码器
.addLast("serverIdleHandler",
new IdleStateHandler(0, 0, serverIdleTimeInSeconds))//设置心跳检测
.addLast("serverHandler",new NettyServerStreamHandler(NettyTcpServer.this, false,
serverStreamLifecycleListeners,
serverStreamMessageListeners));//设置业务处理逻辑
}
});
ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
channelFuture.channel().closeFuture().sync();
}
}
BIO和NIO编程在网络事件发生后都需要进行处理数据的粘包拆包、对完整的数据包进行解码、触发各种统计类事件、对响应消息进行编码、监听各种网络异常、需要对底层网络和通信协议有一定的了解。
定制能力强
通过ChannelHandler灵活扩展
假如每个handler都会把读事件向下一个InboundHandler类型的节点进行传递,此时的调用链路为head->A->B->C->tail;
假如B业务handler处理数据后不把读事件继续向下传递,此时B可以在自己内部选择不向下一个节点传递读事件.此时调用链路变为head->A->B;
假如每个handler都会把读事件向下一个OutboundHandler类型的节点进行传递,当C业务handler发送响应数据时此时调用链路为C->B->head;
假如业务B是参数校验的的headler,当校验失败就响应客户端.此时调用的链路为B->head;
Handler强大
编解码
TCP粘包拆包
编解码Handler
LineBasedFrameDecoder(回车换行分包); DelimiterBasedFrameDecoder(特殊分隔符分包); FixedLengthFrameDecoder(固定长度报文来分包)
编解码字符串的StringEncoder和StringDecoder; 用于处理HTTP协议编解码HttpObjectDecoder和HttpObjectEncoder; 用于处理protobuf编解码ProtobufVarint32FrameDecoder和ProtobufDecoder;
支持多种主流协议
idle(心跳检测)
readerIdleTimeSeconds: 读取空闲时间,即从上次读取数据到现在的秒数。
writerIdleTimeSeconds: 写入空闲时间,即从上次写入数据到现在的秒数。
allIdleTimeSeconds: 读写都空闲的时间,即从上次读写数据到现在的秒数。
高性能
一次网络通信都发生了什么
1.客户端确定需要发送的数据;
2.数据从程序到系统然后通过网卡发送;
3.服务端收到读事件后把数据从系统读取到应用中;
Netty怎么提高通信性能
数据在系统中的存储Netty使用ByteBuf内存池来减少申请内存耗时;
数据在用户态到内核态的传输Netty采用了内存零拷贝(减少用户态到内核态的两次拷贝)来减少传输耗时;
数据在网络中的传输 数据传输时间取决于数据传输速度、数据包数量。对于传输速度无法优化,Netty内置了许多编码器,可以选择对数据压缩比较好的解码器来减少数据包的数量,以此来减少耗时;
服务端对网络事件的处理Netty采用主次Reactor多线程模型来加快对网络事件的处理,传统的BIO不能支持太多网络连接以及对系统资源使用率比较低。传统的NIO当网络连接数超多时网络事件得不到快速响应,造成大量客户端进行重试。
客户端消息处理上,Netty采用了无锁串行化设计思想结合volatile的大量使用;通过读写锁提升并发性能来大大缩短了消息处理时间。
Netty运行原理
整体结构
Core核心层:核心层里Netty最精华的部分,它提供了底层网络通信的通用抽象和实现,包括事件模型、通用API、支持零拷贝的ByteBuf 等;
Protocol Support 协议支持层:协议支持层基本上覆盖了主流协议的编解码实现,Netty 丰富的协议支持降低了用户的开发成本;
Transport Service 传输服务层:传输服务层提供了网络传输能力的定义和实现方法。它支持 Socket、HTTP 隧道等传输方式。Netty 对 TCP、UDP 等数据传输做了抽象和封装,让开发者可以更聚焦在业务逻辑实现上,而不必关系底层数据传输的细节;
逻辑架构
网络通信层
Netty服务端:程序启动时会生成一个ServerBootstrap对象,该对象会生成一个NioServerSocketChannel来监听某一个端口的建立网络连接的事件,当网络连接建立后会监听网络连接的各种事件并通知到事件调度层。
Netty客户端:程序启动的时会生成一个Bootstrap对象,该对象会生成一个NioSocketChannel来与服务端建立网络连接的事件,当网络连接建立后会监听网络连接的各种事件并通知到事件调度层。
事件调度层
EventLoop 负责处理 I/O 事件和调度任务。每一个NioEventLoop内部都有唯一一个Selector,通过这个Selector可以对注册的channel进行网络事件的读取;NioEventLoop 还有一个内部的任务队列,可以用来提交 Runnable 任务。这些任务会在 NioEventLoop 的线程上下文中执行,确保了任务的顺序执行。 EventLoopGroup 本质是一个线程池,负责管理EventLoop。其主要作用有从线程池挑选一个EventLoop进行channe的注册或者提交一个任务、关闭不再使用的 Channel、释放 Selector 和其他相关资源确保应用程序的干净退出。
服务编排层
ChannelHandler主要分为两类,InboundHandler和OutboundHandler
InboundHandler用于处理从网络流入(inbound)的数据和事件。帮助我们处理接收的数据、连接建立、断开等事件。核心方法有channelActive(网络连接建立成功时会调用)、channelInactive(网络连接断连时调用)、channelRead(接受到数据时调用)、exceptionCaught(如果在处理事件或数据时发生异常,该方法会被调用。可以在这里捕获并处理异常,防止应用程序崩溃。)
OutboundHandler用于处理从应用程序流向网络的出站(outbound)操作,如写入数据,发起连接,关闭连接等核心方法有。
write(写入数据到channel时调用),flush(将缓冲区所有未写入数据立即发送出去),connect(尝试建立网络连接时调用)。
ChannelHandlerContext是handler与Netty内部机制交互的主要方式,它使得 handler 可以在不直接访问其他handler的情况下,协同处理I/O事件和数据。同时也是每个ChannelHandler在处理事件时的上下文环境,可以获取到Pipeline、Channel、Allocator等对象。
ChannelPipeline Netty中的关键组件,是一个处理网络I/O事件和数据的有序链表。ChannelPipeline负责将入站(inbound)和出站(outbound)事件分发给链中的各个ChannelHandler,实现了事件驱动的网络编程模型。每个ChannelHandler都有一个唯一的ChannelHandlerContext,用于与ChannelPipeline交互。
运行流程
1.服务端启动的时把ServerSocketChannel注册到boss EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做server EventLoop;
2.当 serverEventLoop中监听到有建立网络连接的事件后会把底层的SocketChannel和serverSocketChannel封装成为NioSocketChannel;
3.开始把自定义的ChannelHandler加载到NioSocketChannel 里的pipeline中,然后把该NioSocketChannel注册到worker EventLoopGroup中某一个EventLoop上,暂时把这个EventLoop叫做worker EventLoop;
4.worker EventLoop开始监听NioSocketChannel上所有网络事件;
结语
微信扫码关注该文公众号作者