netty官方文档:https://netty.io/wiki/index.html
netty文章参考:【硬核】肝了一月的Netty知识点(强烈推荐!敖丙大佬的超详细源码分析)
本文概括了netty的常用类方法以及常见问题,包括使用kryo实现自定义序列化,如果希望了解更多源码知识,可以去看敖丙大佬写的netty文章
Netty执行流程
Netty 核心组件
Channel
Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。
EventLoop 与 EventLoopGroup
EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。
EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。
Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。
一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。
ServerBootstrap 与 Bootstrap
Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。
Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。
ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。
ChannelHandler 与 ChannelPipeline
ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。
ChannelFuture
Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。
Netty - Server 代码
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 创建parentGroup和childGroup分别用来监听和操作
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
// 创建服务启动类
ServerBootstrap bootstrap = new ServerBootstrap();
// 绑定两个EventLoopGroup(类似于线程池的线程)
bootstrap.group(parentGroup, childGroup)
//告诉Netty启动服务时将使用哪个Channel进行数据处理
.channel(NioServerSocketChannel.class)
//把childGroup中的一个线程与处理器进行绑定
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new SomeSocketServerHandler());
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("服务器已启动。。。");
future.channel().closeFuture().sync();
} finally {
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
Server 端 Handler代码
public class DemoSocketServerHandler
extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("Client Address ====== " + ctx.channel().remoteAddress());
ctx.channel().writeAndFlush("from server:" + UUID.randomUUID());
ctx.fireChannelActive();
TimeUnit.MILLISECONDS.sleep(500);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Netty - Client 代码
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new DemoSocketClientHandler());
}
});
ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
future.channel().closeFuture().sync();
} finally {
if(eventLoopGroup != null) {
eventLoopGroup.shutdownGracefully();
}
}
}
}
Client 端 Handler代码
public class DemoSocketClientHandler
extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println(msg);
ctx.channel().writeAndFlush("from client: " + System.currentTimeMillis());
TimeUnit.MILLISECONDS.sleep(5000);
}
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception {
ctx.channel().writeAndFlush("from client:begin talking");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
常见问题
netty中ServerBootstrap的bind方法有什么用?sync()方法有用什么用?
Netty的ServerBootstrap类中的bind()方法主要用于绑定服务器到特定的地址和端口。当你调用bind()方法时,Netty会尝试将服务器绑定到指定的地址和端口。如果绑定成功,它将返回一个Channel对象,你可以使用这个对象来处理入站数据。
sync()方法则是用于同步等待服务器绑定操作完成。当调用bind()方法时,Netty实际上会在后台线程上异步地完成绑定操作,但是如果你需要等待这个操作完成,可以使用sync()方法。这将阻塞当前线程,直到绑定操作完成。这对于需要在绑定操作完成后执行某些操作的代码非常有用。
总的来说,bind()方法用于设置服务器的监听地址和端口,而sync()方法用于等待绑定操作完成。在Netty中,这两个方法通常一起使用,以便在服务器绑定到特定地址和端口后执行其他操作。
Handle可以重写哪些方法?这些方法的作用是什么?
//通道读取事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println(msg);
ctx.channel().writeAndFlush("from client: " + System.currentTimeMillis());
TimeUnit.MILLISECONDS.sleep(5000);
}
//当一个新的连接被注册到ChannelPipeline时,这个事件会被触发。在这个方法中,你可以进行一些初始化操作,比如加载配置、启动服务等。
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
}
//当一个连接被注销(即关闭)时,这个事件会被触发。在这个方法中,你可以进行一些清理操作,比如关闭连接、清理资源等。
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
}
//当连接被激活(即,服务器已经成功绑定到指定的地址和端口)后,这个方法会被调用。这是你开始接收入站消息的合适时机。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
//连接未激活
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
// 当一个入站消息已经被完全处理后,这个方法会被调用。通常,你可以用这个方法来通知你的业务逻辑已经处理完了所有消息。
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
// 当Netty发出的事件被触发时,这个方法会被调用。一般情况下,你可以在实现自己的handler时在这个方法里面加入自己的逻辑,如心跳检测、业务处理等。
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
}
//当channel的写可操作性改变时,这个事件会被触发。一般情况下,写可操作性发生变化表明可能可以写入数据了。在实现自己的handler时,你也可以在这个方法中加入逻辑来响应这种变化,如更新写入进度等。
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
super.channelWritabilityChanged(ctx);
}
// 通道发生异常事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("client caught exception", cause);
ctx.close();
}
大多数可以根据字面意思理解,可以按需求进行重写
netty中ServerBootstrap的channel方法有什么用?
在Netty中,ServerBootstrap是一个框架的核心类,它提供了一个用户编写的ChannelHandler以及线程管理逻辑被执行的方式。而channel()方法是在使用ServerBootstrap启动服务器时的一个非常常用的方法,它的作用是将Channel连接到给定的参数,这里主要可以理解为你需要告诉Netty启动服务时将使用哪个Channel进行数据处理。
至于参数,主要就是传入的Channel实现了哪个ChannelFactory,它会根据网络连接或者已经存在的连接对象生成新的Channel实例。另外这个参数还决定了使用的类型和它的各种参数配置,包括队列长度,并发度等等。
Netty中Handler处理器类型有哪些,分别有什么作用?
ChannelInboundHandler:用于处理 入站 I/O 事件。
ChannelOutboundHandler:用于处理 出站 I/O 操作。
ChannelInboundHandlerAdapter:用于处理 入站 I/O 事件。
ChannelOutboundHandlerAdapter:用于处理 出站 I/O 操作。
ChannelDuplexHandler:可以处理 入站和出站 事件。
使用kryo做我们的自定义序列化器
前文我们也是介绍了kryo的实战使用,那么我们就把netty和kryo一起结合使用一下
NettyKryoDecoder 解码器类
@AllArgsConstructor
@Slf4j
public class NettyKryoDecoder extends ByteToMessageDecoder {
private final Serializer serializer;
private final Class<?> genericClass;
//Netty传输的消息长度也就是对象序列化后对应的字节数组的大小,存储在 ByteBuf 头部
private static final int BODY_LENGTH = 4;
/**
* 解码 ByteBuf 对象
* @param ctx 解码器关联的 ChannelHandlerContext 对象
* @param in "入站"数据,也就是 ByteBuf 对象
* @param out 解码之后的数据对象需要添加到 out 对象里面
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
//1.byteBuf中写入的消息长度所占的字节数已经是4了,所以 byteBuf 的可读字节必须大于 4,
if (in.readableBytes() >= BODY_LENGTH) {
//2.标记当前readIndex的位置,以便后面重置readIndex 的时候使用
in.markReaderIndex();
//3.读取消息的长度//注意: 消息长度是encode的时候我们自己写入的,参见 NettyKryoEncoder 的encode方法
int dataLength = in.readInt();
//4.遇到不合理的情况直接 return
if (dataLength < 0 || in.readableBytes() < 0) {
log.error("data length or byteBuf readableBytes is not valid");
return;
}
//5.如果可读字节数小于消息长度的话,说明是不完整的消息,重置readIndex
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
// 6.走到这里说明没什么问题了,可以序列化了
byte[] body = new byte[dataLength];
in.readBytes(body);
// 将bytes数组转换为我们需要的对象
Object obj = serializer.deserialize(body, genericClass);
out.add(obj);
log.info("successful decode ByteBuf to Object");
}
}
}
NettyKryoEncoder 编码器类
@AllArgsConstructor
public class NettyKryoEncoder extends MessageToByteEncoder<Object> {
private final Serializer serializer;
private final Class<?> genericClass;
//将对象转换为字节码然后写入到 ByteBuf 对象中
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) {
if (genericClass.isInstance(o)) {
// 1. 将对象转换为byte
byte[] body = serializer.serialize(o);
// 2. 读取消息的长度
int dataLength = body.length;
// 3.写入消息对应的字节数组长度,writerIndex 加 4
byteBuf.writeInt(dataLength);
//4.将字节数组写入 ByteBuf 对象中
byteBuf.writeBytes(body);
}
}
}
修改之前的netty client以及server代码
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
修改后NettyClient代码:
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
private final String host;
private final int port;
private static final Bootstrap b;
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
// 初始化相关资源比如 EventLoopGroup, Bootstrap
static {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
b = new Bootstrap();
KryoSerializer kryoSerializer = new KryoSerializer();
b.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
/*自定义序列化编解码器*/
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
ch.pipeline().addLast(new NettyClientHandler());
}
});
}
/*** 发送消息到服务端** @param rpcRequest 消息体* @return 服务端返回的数据*/
public RpcResponse sendMessage(RpcRequest rpcRequest) {
try {
ChannelFuture f = b.connect(host, port).sync();
logger.info("client connect {}", host + ":" + port);
Channel futureChannel = f.channel();
logger.info("send message");
if (futureChannel != null) {
futureChannel.writeAndFlush(rpcRequest).addListener(future -> {
if (future.isSuccess()) {
logger.info("client send message: [{}]", rpcRequest.toString());
} else {
logger.error("Send failed:", future.cause());
}
});
futureChannel.closeFuture().sync();
AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
return futureChannel.attr(key).get();
}
} catch (InterruptedException e) {
logger.error("occur exception when connect server:", e);
}
return null;
}
}
修改后NettyServer代码:
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
KryoSerializer kryoSerializer = new KryoSerializer();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
// TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。
.childOption(ChannelOption.TCP_NODELAY, true)
// 是否开启 TCP 底层心跳机制
.childOption(ChannelOption.SO_KEEPALIVE, true)
//表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数
.option(ChannelOption.SO_BACKLOG, 128)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcRequest.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyServerHandler());
}
});
// 绑定端口,同步等待绑定成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error("occur exception when start server:", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
这样kryo序列化器就会注入到编码以及解码器中,同时会把他转化为RpcResponse以及RpcRequest类
kryo序列化器代码可以看前文:常见的序列化协议以及高性能Kryo实战
然后我们开始测试
添加测试类以及测试代码
@Test
void test01() {
new NettyServer(8889).run();
}
@Test
void test02() {
RpcRequest rpcRequest = RpcRequest.builder().interfaceName("interface").methodName("hello").build();
NettyClient nettyClient = new NettyClient("127.0.0.1", 8889);
for (int i = 0; i < 3; i++) {
nettyClient.sendMessage(rpcRequest);
}
RpcResponse rpcResponse = nettyClient.sendMessage(rpcRequest);
System.out.println(rpcResponse.toString());
}
客户端控制台输出
服务端控制台输出
成功!