文章目录
为什么要用 Netty
Netty的定义
Netty是由JBOSS提供的一个java开源框架,Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序 |
为什么要用 Netty
1、虽然 JAVA NIO 框架提供了 多路复用 IO 的支持,但是并没有提供上层“信息格式” 的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON 这些信息格式的封装,但是 Netty 框架提供了这些数据格式封装(基于责任链模式的编码和解码功能); 2、NIO 的类库和 API 相当复杂,使用它来开发,需要非常熟练地掌握 Selector、ByteBuffer、ServerSocketChannel、SocketChannel 等,需要很多额外的编程技能来辅助使用 NIO,例如,因为 NIO 涉及了Reactor 线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的 NIO 程序; 3、要编写一个可靠的、易维护的、高性能的 NIO 服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等, 这些 Netty 框架都提供了响应的支持; 4、Netty 的性能很高,按照 Facebook 公司开发小组的测试表明,Netty 最高能达到接近百万的吞吐: |
HelloWorld程序
服务端
public class NettyServerDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("服务器开始启动");
(new NettyServerDemo()).start();
System.out.println("服务器关闭");
}
private void start() throws InterruptedException {
final NettyServerHandler wNettyServerHandler = new NettyServerHandler();
// 线程组
EventLoopGroup wEventLoopGroup = new NioEventLoopGroup();
// 服务端启动器
ServerBootstrap wServerBootstrap = new ServerBootstrap();
try {
wServerBootstrap
.group(wEventLoopGroup)
.channel(NioServerSocketChannel.class) // 指定使用Nio通讯模式
.localAddress(NettyConstant.DEFAULT_PORT) // 指定监听端口
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(wNettyServerHandler);
}
});
// 异步绑定服务器, sync会阻塞直到完成。
ChannelFuture wChannelFuture = wServerBootstrap.bind().sync();
// 阻塞了当前线程,直到服务器的Channel被关闭
wChannelFuture.channel().closeFuture().sync();
} finally {
wEventLoopGroup.shutdownGracefully().sync();
}
}
}
/*允许ChannelHandler在多个ChannelPipeline中进行共享*/
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf wByteBuf = (ByteBuf) msg;
System.out.println("server accept, message: " + wByteBuf.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush(wByteBuf);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
public class NettyClientDemo {
public static void main(String[] args) throws InterruptedException {
(new NettyClientDemo()).start();
}
private void start() throws InterruptedException {
final NettyClientHandler wNettyClientHandler = new NettyClientHandler();
// 线程池
EventLoopGroup wEventLoopGroup = new NioEventLoopGroup();
// 客户端启动器
Bootstrap wBootstrap = new Bootstrap();
try {
wBootstrap
.group(wEventLoopGroup)
.channel(NioSocketChannel.class) // 指定使用Nio通讯模式
.remoteAddress(new InetSocketAddress(NettyConstant.DEFAULT_SERVER_IP, NettyConstant.DEFAULT_PORT)) // 对端Ip和端口
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(wNettyClientHandler);
}
});
// 异步连接到服务器, sync会阻塞直到完成。
ChannelFuture wChannelFuture = wBootstrap.connect().sync();
// 阻塞了当前线程,直到服务器的Channel被关闭
wChannelFuture.channel().closeFuture().sync();
} finally {
wEventLoopGroup.shutdownGracefully().sync();
}
}
}
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 读取到返回数据后进行业务处理
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("client Accept, message: " + msg.toString(CharsetUtil.UTF_8));
}
/**
* channel 活跃后, 做业务处理
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("HelloWorld", CharsetUtil.UTF_8));
}
}
设置的常量
public class NettyConstant {
public static final Integer DEFAULT_PORT = 9999;
public static final String DEFAULT_SERVER_IP = "127.0.0.1";
}
Netty组件
Channel 接口
基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其中基本的构造是类 Socket。Netty 的 Channel 接口所提供的 API,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。 |
Channel 的生命周期状态
ChannelUnregistered:Channel 已经被创建,但还未注册到 EventLoop ChannelRegistered:Channel 已经被注册到了 EventLoop ChannelActive:Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了 ChannelInactive:Channel 没有连接到远程节点 |
EventLoop 和 EventLoopGroup
EventLoop
|
线程管理
在内部,当提交任务到如果 (当前)调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件。 |
ChannelFuture 接口
Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了 ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。 可以将 ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。 |
ChannelHandler
ChannelHandler 接口
从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。 Netty 定义了下面两个重要的 ChannelHandler 子接口: ChannelInboundHandler ——处理入站数据以及各种状态变化; ChannelOutboundHandler ——处理出站数据并且允许拦截所有的操作。 |
ChannelHandler 子类图
ChannelPipeline 和 ChannelHandlerContext
ChannelPipeline 接口
当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的。在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。 使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。 |
ChannelHandler 在ChannelPipeline的生命周期
在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。 handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用 handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用 exceptionCaught当处理过程中在 ChannelPipeline 中有错误产生时被调用 |
ChannelPipeline 中 ChannelHandler
ChannelHandlerContext
通过使用作为参数传递到每个方法的 ChannelHandlerContext,事件可以被传递给当前ChannelHandler 链中的下一个 ChannelHandler。虽然这个对象可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。 ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个ChannelPipeline 中的其他 ChannelHandler 之间的交互。 |
ChannelHandlerContext、channel、ChannelPipeline, write方法的区别
Bootstrap和ServerBootstrap
Bootstrap用于客户端,ServerBootstrap用于服务端。ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而 Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。 引导一个客户端只需要一个 EventLoopGroup,但是一个ServerBootstrap 则需要两个。因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的 Channel |
ChannelOption
ChannelOption 的各种属性在套接字选项中都有对应。 1、ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小 2、ChannelOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口。 比如,某个服务器进程占用了 TCP 的 80 端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用,比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置 SO_REUSEADDR就无法正常使用该端口。 3、ChannelOption.SO_KEEPALIVE 参数对应于套接字选项中的 SO_KEEPALIVE,该参数用于设置 TCP 连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。当设置该选项以后,如果在两小时内没有数据的通信时,TCP 会自动发送一个活动探测数据报文。 4、ChannelOption.SO_SNDBUF 和 ChannelOption.SO_RCVBUF 。ChannelOption.SO_SNDBUF 参数对应于套接字选项中的 SO_SNDBUF,ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF 这两个参数用于操作接收缓冲区和发送缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功,发送缓冲区用于保存发送数据,直到发送成功。 5、ChannelOption.SO_LINGER 参数对应于套接字选项中的SO_LINGER,Linux内核默认的处理方式是当用户调用 close()方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close()的调用时间,直到数据完全发送 6、ChannelOption.TCP_NODELAY 参数对应于套接字选项中的 TCP_NODELAY,该参数的使用与 Nagle 算法有关,Nagle 算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用 Nagle 算法,使用于小数据即时传输,于 TCP_NODELAY 相对应的是 TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。 |
ByteBuf
使用模式
1、堆缓冲区 。最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。可以由 hasArray()来判断检查 ByteBuf 是否由数组支撑。如果不是,则这是一个直接缓冲区。 2、直接缓冲区 。从JVM占用内存外分配一块内存,分配和释放都较为昂贵,但读写效率相对较高。 3、复合缓冲区 。它为多个 ByteBuf 提供一个聚合视图。比如 HTTP 协议,分为消息头和消息体,这两部分可能由应用程序的不同模块产生,各有各的 ByteBuf,将会在消息被发送的时候组装为一个 ByteBuf,此时可以将这两个 ByteBuf 聚合为一个CompositeByteBuf,然后使用统一和通用的 ByteBuf API 来操作。 |
分配
1、ByteBufAllocator接口 。Netty 提供了两种 ByteBufAllocator 的实现:PooledByteBufAllocator 和Unpooled-ByteBufAllocator。前者池化了 ByteBuf 的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化 ByteBuf 实例,并且在每次它被调用时都会返回一个新的实例。 2、Unpooled 缓冲区 ,一个Netty提供的工具类。提供了静态的辅助方法来创建未池化的 ByteBuf 实例 |