前言
一系列文章旨在把我学习Netty以及使用Netty的一些经验分享出来,希望能帮助到对Netty感兴趣或正要上手的同学们。如果发现我述说得有问题,希望大家能指出讨论,非常感谢。
引导
首先我们要提出几个问题,大家一起思考,然后再通过文章来加深理解。
- 什么是Netty
- Netty的诞生是为了解决什么问题
- Netty的核心组件有哪些
什么是Netty
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty基于IO多路复用模型,对Java提供的NIO进行了更加完善的包装,解决NIO存在的bug,使用者无需关注太多底层。成熟、稳定、高并发、低延迟、易上手等特点已经获得很多开发者的青睐。
它有以下特性:
- 设计
- 统一API,支持多种传输类型,阻塞和非阻塞
- 真正的无连接数据报套接字支持
- 链接逻辑组件以支持复用
- 性能
- 拥有比Java的NIO更高的吞吐量以及更低的延迟
- 得益于池化和复用,拥有更低的资源消耗
- 健壮性
- 不会因为慢速、快速或者超载的连接而导致OutOfMemoryError
- 消除在高速网络中NIO应用程序常见的不公平读/写比率
- 安全性
- 完整的SSL/TLS以及StartTLS支持
Netty的诞生为了解决什么问题
大家已经知道Netty是基于Java NIO的基础上实现的(需要区别Java NIO 以及NIO,前者是基于IO多路复用实现,后者一般指是None Blocking IO,非阻塞IO),主要原因在于Java NIO存在多方面缺陷:
- 复杂的API、类库。Selector、ServerSocketChannel、SocketChannel、ByteBuffer等,想写出优秀高质量的NIO程序你就需要对网络编程这块非常熟悉才行。
- 针对半包读写,网络阻塞,编码处理等等,NIO要实现的难度比较大
- 存在bug,最为代表性的是Epoll空轮询,导致CPU利用率爆满。
由此,Netty就诞生了,它不单单解决了NIO的缺陷,还增加了多种功能支持,例如快速切换NIO和BIO,自定义编码解码,原生也提供了多种类库,以支持多种协议,包括TCP\UDP\MQTT\Protocol Buffers 等等。还有它高并发,低延迟的特性,让使用者能快速,方便上手,专心处理业务而无需关注性能和漏洞问题,因为Netty都已经帮你处理好这些东西。
Netty的核心组件
上面我们知道了什么是Netty以及Netty能给我们带来什么好处,下面我们就来看一下Netty的核心组件。
- Channel
- 回调
- Future
- 事件和ChannelHandler
Channel
Channel是Java NIO的一个基本构造,它代表到一个实体(实体包括硬件设备、文件、网络套接字等)的开放连接,可以进行读操作或写操作。简单来说,它就是两者之间可以进行数据交互的一条通道,不论是入站或出站数据(入站出站可以理解成数据的传出传入),都通过channel来传输。它可以被打开或关闭,连接或断开链接。
回调
进行多线程异步开发的人对回调这个名词应该不陌生,通俗来讲,它就是在某一个操作结束之后要执行的方法,方法我们可以自定义,可以包括通知、异常处理等等。 在Netty中很多地方都使用了回调,举个例子:ChannelInboundHandler中的channelActive方法,当一个新的连接已经建立的时候,该方法就会被执行。
/**
* @author chenws
* @date 2020/03/17 11:01:33
*/
@Slf4j
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("client-{}已经成功连接。",ctx.channel().remoteAddress());
super.channelActive(ctx);
}
}
Future
Future提供了另一种在操作完成时通知应用程序的方式,你可以把它看作是一个异步操作结果的占位符,它将在未来某一时刻完成,并可以访问其结果。
Java NIO与之对应的是java.util.concurrent.Future,但是它实现的Future需要手动去检查对应的操作是否已经完成,或者一直阻塞直至它完成,这样的话即麻烦又耗资源,因此Netty在java Future的基础上编写了自己的一套支持异步读取的Future。
直接看例子 listener,监听事件,根据状态来判断操作是否成功,不成功的话可以打印错误信息。
/**
* @author chenws
* @date 2020/03/17 11:27:02
*/
@Slf4j
public class BindListener implements GenericFutureListener<Future<? super Void>> {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
boolean success = future.isSuccess();
if(success) {
log.info("bind操作完成");
}else {
Throwable cause = future.cause();
cause.printStackTrace();
}
}
}
绑定操作
/**
* @author chenws
* @date 2020/03/17 11:22:14
*/
@Slf4j
public class BindServer {
public void startServer() throws InterruptedException {
ServerBootstrap b = new ServerBootstrap();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(6);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
}
});
BindListener bindListener = new BindListener();
//addListener操作后,当该程序绑定指定ip和端口号后,就会执行配置的listener
Channel channel = b.bind("localhost", 8080).sync().addListener(bindListener).channel();
}
}
你可以把Netty的Future看作是回调的一个更加精细的版本,回调和Future是互相补充的机制,它们互相结合,构成Netty关键构建块之一。
事件和ChannelHandler。
首先我们先理解下这两个名词的定义
- 事件:可以看作是有入站或出站数据而触发的事件
- ChannelHandler:是我们自定义的一系列处理器,包括但不仅限:编码解码器,记录日志,数据转换,流控制以及我们的业务逻辑处理。
事件入站或出站后通过一系列的ChannelHandler处理数据以达到我们的目的,而ChannelHandler就是通过ChannelPipeline管理起来,它能使我们的事件按顺序的执行ChannelHandler。正是这种设计,使我们的代码耦合度降低,且灵活、方便、易懂。
小结
在本章中,我们主要了解Netty的功能、优点以及核心组件,下一章我们将介绍Netty的相关组件,包括Channel、EventLoop、ChannelFuture。在此之前,大家可以参照我Github上的 Netty-TCP例子来先了解一个Netty应用程序。