WIKI
netty Wiki · GitHubhttps://github.com/netty/netty/wiki
Maven坐标
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
入门案例
服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LoggingHandler;
import java.util.Random;
/**
* @author Jay
*/
public class NettyServer {
private static NioEventLoopGroup boos = new NioEventLoopGroup();
private static NioEventLoopGroup childGroup = new NioEventLoopGroup(2);
public static void main(String[] args) throws InterruptedException {
new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.group(boos, childGroup)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// helloWord(ch);
// withChildGroup(ch);
// testHandlers(ch);
}
})
.bind(8080);
}
/**
* netty 入门示例
*
* @param ch
*/
private static void helloWord(SocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
});
}
private static void withChildGroup(SocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = msg instanceof ByteBuf ? ((ByteBuf) msg) : null;
if (byteBuf == null)
return;
byte[] dst = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(dst);
System.out.println(new String(dst));
super.channelRead(ctx, msg);
}
});
}
}
客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
/**
* @author Jay
*/
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
new Bootstrap()
.channel(NioSocketChannel.class)
.group(new NioEventLoopGroup())
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect("localhost", 8080)
.sync().channel()
.writeAndFlush(args != null ? args[0] : "Hello Word");
}
}
💡 提示
一开始需要树立正确的观念
把 channel 理解为数据的通道
把 msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf
把 handler 理解为数据的处理工序
工序有多道,合在一起就是 pipeline,pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
handler 分 Inbound 和 Outbound 两类
把 eventLoop 理解为处理数据的工人
工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
组件
Channel &ChannelFuture(close) &Promise
- I/O 事件和请求是在 ChannelPipeline 中完成的.
- io.netty.channel.Channel 的操作是异步的
- SocketChannel 有父 是ServerSocketChannel [TCP]
- 用完 Channel后调用close(),关闭资源
通过Bootstrap(ServerBootstrap)初始化好连接之后 调用 channel()即返回ChannelFuture 对象,通过sync() 会阻塞等待直到当前线程获取到Channel,还可以使用回调的方式
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import java.util.Scanner;
@Slf4j
public class CloseFutureClient {
private static NioEventLoopGroup group;
public static void main(String[] args) throws InterruptedException {
group = new NioEventLoopGroup();
ChannelFuture channelFuture = new Bootstrap()
.channel(NioSocketChannel.class)
.group(group)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new StringEncoder());
}
})
.connect("localhost", 8080);
Channel channel = channelFuture.sync().channel();
log.debug("{}", channel);
new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (true) {
String line = scanner.next();
if (line.equals("q")) {
channel.close();
channel.close();
// debug 与 close 不是同一线程执行放的无法保证顺序
// log.debug("close");
break;
}
channel.writeAndFlush(line);
}
}).start();
// * 推荐两种种方式关闭 channel
if (Math.random() > 0.5)
closeWithSync(channel);
else
closeWithListener(channel);
// channel 关闭但线程池没有结束?
// 获取 group 对象 调用 shutdownGracefully() [在 closeFuture 中添加方法调用]
}
// 通过向Channel中添加回调的方式执行操作
private static void closeWithListener(Channel channel) {
ChannelFuture closeFuture = channel.closeFuture();
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
log.debug("关闭之后的操作");
group.shutdownGracefully();
}
});
}
// 同步阻塞方式获取Channel
private static void closeWithSync(Channel channel) throws InterruptedException {
channel.closeFuture().sync();
log.debug("close");
}
}
💡 异步提升的是什么
-
单线程没法异步提高效率,必须配合多线程、多核 cpu 才能发挥异步的优势
-
异步并没有缩短响应时间,反而有所增加
-
合理进行任务拆分,也是利用异步的关键
io.netty.util.concurrent.Promise
简单使用
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultPromise;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
@Slf4j
public class TestNettyPromise {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 此处仅作为线程池使用
NioEventLoopGroup executorGroup = new NioEventLoopGroup();
// 为 Promise 指定一个 线程
final DefaultPromise<Integer> promise = new DefaultPromise(executorGroup.next());
//
Runnable task = () -> {
try {
log.debug("开始计算");
Thread.sleep(3000L);
if (Math.random() > 0.5)
throw new RuntimeException("Random number generator");
promise.trySuccess(80);
} catch (Exception e) {
e.printStackTrace();
promise.setFailure(e);
}
};
// 创建任务并提交给一个新的线程
new Thread(task, "MyThread").start();
log.debug("获取结果:{},", promise.get());
}
}
ChannelHandler & ChannelPipeline
io.netty.channel.ChannelHandler
打个比喻,每个 Channel 是一个产品的加工车间,Pipeline 是车间中的流水线,ChannelHandler 就是流水线上的各道工序,而后面要讲的 ByteBuf 是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品
ChannelHandler 使用了拦截过滤的设计模式核心 J2EE 模式 - 拦截过滤器 (oracle.com)https://www.oracle.com/java/technologies/intercepting-filter.html
在Netty中,"Inbound"和"Outbound"是用于描述网络数据流动方向的术语。
"Inbound"表示数据从外部流向应用程序。也就是说,数据从网络中进入Netty的通道或管道,经过一系列的处理后,最终传递给应用程序处理。例如,当接收到网络数据包时,数据会通过一系列的Inbound处理器(如解码器)进行解析和处理,然后交给应用程序进行进一步的业务逻辑处理。
"Outbound"表示数据从应用程序流向外部。也就是说,应用程序产生的数据经过一系列的处理后,通过Netty的通道或管道发送到网络。例如,应用程序需要发送一个消息给远程主机,数据会经过一系列的Outbound处理器(如编码器)进行处理和编码,然后通过网络发送到目标主机。
ctx.channel().write(msg)
会使用完整的ChannelPipeline
进行处理,而ctx.write(msg)
会从当前位置往上(往前)依次寻找出站处理器并执行。
/**
* 测试 入站&出站处理器的执行时的排序问题
* 测试 ctx.channel().write(msg) 与 ctx.write(msg) 区别
* @param ch
*/
private static void testHandlers(SocketChannel ch) {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.print("入站处理器1 \t");
ctx.fireChannelRead(msg); // 1
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("入站处理器2 \t");
ctx.fireChannelRead(msg); // 2
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.print("使用 OutboundHandler 1\t");
ctx.write(msg, promise); // 4
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.print("入站处理器3 \n");
if (Math.random() > 0.5F) {
System.out.println("使用 ctx.channel().write(msg)");
ctx.channel().write(msg); // 3
}else{
System.out.println("使用 ctx.write(msg)");
ctx.write(msg);
}
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.print("使用 OutboundHandler 2\t");
ctx.write(msg, promise); // 5
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.print("使用 OutboundHandler 3 \n");
ctx.write(msg, promise); // 6
}
});
}
测试ChannelPipline 的便捷方式
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 对 ChannelHandler 的便捷测试方法
* @author Jay
*/
public class ChannelPipelineTest {
@Test
public void testChannelPipeline() {
// 创建 EmbeddedChannel 对象
EmbeddedChannel channel = new EmbeddedChannel(
new HttpRequestEncoder(),
new HttpResponseDecoder(),
new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("入站处理器1");
ctx.fireChannelRead(msg);
}
},
new ChannelOutboundHandlerAdapter() {
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println("出站处理器1");
ctx.write(msg, promise);
}
}
);
// 发送输入消息
channel.writeInbound("request");
channel.writeOutbound("response");
// 断言处理结果
assertTrue(channel.finish());
assertEquals("request", channel.readInbound());
assertEquals("response", channel.readOutbound());
// ... 进一步验证其他期望的结果
}
}
ChannelInboundHandler 的参考示例 io.netty.channel.SimpleChannelInboundHandler 入站(读取消息的 Handler 注意 ByteBuf 的释放)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
ByteBuf 与java.nio.ByteBuffer
retain & release
由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。
-
UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
-
UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
-
PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存
回收内存的源码实现,请关注下面方法的不同实现
protected abstract void deallocate();
Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口
-
每个 ByteBuf 对象的初始计数为 1
-
调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
-
调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
-
当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用谁来负责 release 呢?
不是我们想象的(一般情况下)
ByteBuf buf = ...
try {
...
} finally {
buf.release();
}
因为 pipeline 的存在,一般需要将 ByteBuf 传递给下一个 ChannelHandler,如果在 finally 中 release 了,就失去了传递性(当然,如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命,那么便无须再传递)
基本规则是,谁是最后使用者,谁负责 release,详细分析如下
-
起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
-
入站 ByteBuf 处理原则
-
对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
-
将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
-
如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
-
注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
-
假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
-
-
出站 ByteBuf 处理原则
-
出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
-
-
异常处理原则
-
有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true
-
如果使用了 ByteBuf 的池化(PooledByteBufAllocator)功能,需要在操作完成后将 ByteBuf 归还到池中。这可以通过调用 ReferenceCountUtil.release(msg) 方法来实现,该方法会自动处理归还操作。
ByteBuf的自动释放http://t.csdn.cn/YBHfj
io.netty.channel.SimpleChannelInboundHandler#channelRead 释放消息.
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
// 如果仅处理 String 类型的数据,我们仅需要继承 SimpleChannelInboundHandler
// 重写 channelRead0 即可
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
TailContext 释放未处理消息逻辑
// io.netty.channel.DefaultChannelPipeline#onUnhandledInboundMessage(java.lang.Object)
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
具体代码
// io.netty.util.ReferenceCountUtil#release(java.lang.Object)
public static boolean release(Object msg) {
if (msg instanceof ReferenceCounted) {
return ((ReferenceCounted) msg).release();
}
return false;
}
EventLoop
一个EventLoop如果有group 一定是属于一个EventLoopGroup,EventLoop可能管理一个以上的Channel.
关闭 Netty 应用程序通常就像关闭通过 创建的所有 EventLoopGroup一样简单。它返回一个 future,当 EventLoopGroup 已完全终止并且属于该组的所有通道都已关闭时,它会通知您。shutdownGracefully()