netty介绍
-Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github 上的独立项目。
-Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络 IO 程序。
-Netty 主要针对在 TCP 协议下,面向 Client 端的高并发应用,或者 Peer-to-Peer 场景下的大量数据持续传输的应用。
-Netty 本质是一个 NIO 框架,适用于服务器通讯相关的多种应用场景。
-要透彻理解 Netty,需要先学习 NIO,这样我们才能阅读 Netty 的源码。
pom依赖
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version>
</dependency>
</dependencies>
HelloServer
package com.example.netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageCodec;
import io.netty.handler.codec.http.websocketx.WebSocket00FrameDecoder;
import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig;
import io.netty.handler.codec.http.websocketx.WebSocketFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.Charset;
public class HelloServer {
public static void main(String[] args) throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
//这里创建两个线程组 bossGroup 和 workerGroup,bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成
serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(10));
//使用NioServerSocketChannel作为服务器的通道实现
serverBootstrap.channel(NioServerSocketChannel.class);
//
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
//创建一个通道初始化对象
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//给pipeline 设置处理器
ChannelPipeline pipeline = nioSocketChannel.pipeline();//
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler1 || ");
msg = "你的消息为: "+msg.toString()+" handler1 || ";
ctx.fireChannelRead(msg);
}
});
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("handler2 || ");
msg = msg.toString()+"handler2 || ";
ctx.channel().write(msg);
}
});
pipeline.addLast(new ChannelOutboundHandlerAdapter() {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("handler3 || ");
msg = msg.toString()+"handler3 || ";
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(msg.toString().getBytes());
ctx.writeAndFlush(buf);
}
});
pipeline.addLast(new ChannelOutboundHandlerAdapter() {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//handler4 || 经过了这个handler
System.out.println("handler4 || ");
msg = msg.toString()+"handler4 || ";
ctx.writeAndFlush(msg);
}
});
}
});
//
serverBootstrap.bind(8080);
}
}
HelloCilent
package com.example.netty.simple;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
//
NioEventLoopGroup group = new NioEventLoopGroup();
//注意客户端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
// 设置客户端通道的实现类(反射)
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// ByteBuf buffer = (ByteBuf) msg;
System.out.println(msg.toString());
}
});
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8080).sync().channel();
channel.closeFuture().addListener(future -> {
group.shutdownGracefully();
});
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (true) {
String line = scanner.nextLine();
if ("q".equals(line)) {
channel.close();
break;
}
// channel.write(line);
channel.writeAndFlush(line);
}
}).start();
}
}
运行结果
核心组件说明
核心serverBootstrap
常用于服务端,服务端启动引导类
核心bootstrap
常用于客户端,客户端的启动引导类
核心eventLoop
eventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理 Channel 上源源不断的 io 事件。
核心eventLoopGroup
eventLoopGroup 是一组 eventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理。其中BossGroup 专门负责接收客户端的连接,WorkerGroup 专门负责网络的读写
核心channel
channel 有一点类似于 stream,它就是读写数据的双向通道。一个客户端对应一个channel
核心Pipeline 和 ChannelPipeline
Channel 都有且仅有一个 ChannelPipeline 与之对应。ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作。ChannelHandler 被连成一串,就是 Pipeline
核心ChannelHandler
ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作
入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工
核心ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。即 ChannelHandlerContext 中包含一个具体的事件处理器 ChannelHandler,同时 ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler 进行调用。
ctx.channel().write(msg) vs ctx.write(msg)
ctx.channel().write(msg) 从尾部开始查找出站处理器
ctx.write(msg) 是从当前节点找上一个出站处理器
示意图如下
核心bytebuf
是对字节数据的封装
下面代码创建了一个默认的 ByteBuf(池化基于直接内存的 ByteBuf),初始容量是 10
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
log(buffer);
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
直接内存 VS 堆内存
netty默认使用的是直接内存
//可以使用下面的代码来创建池化基于堆的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
//也可以使用下面的代码来创建池化基于直接内存的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
池化 VS 非池化
netty默认池化
池化的最大意义在于可以重用 ByteBuf,有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率,总的来说池化优于非池化
ByteBuf 由四部分组成
查看
通过getClass();来查看是否池化和用的直接内存还是堆内存
ByteBuf的回收
由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。
- UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
- UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
- PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存
Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口
- 每个 ByteBuf 对象的初始计数为 1
- 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
- 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
- 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用
netty会自动的帮我们释放bytebuf
handler中有默认的headHandler和tailHandler帮我们释放bytebuf对象