文章目录
1 基本说明
-
netty的组件设计:Netty的主要组件有 Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
-
ChannelHandler 充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannelInboundHandler 接口(或ChannelInboundHandlerAdapter),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。业务逻辑通常写在一个或者多个 ChannelInboundHandler中。ChannelOutboundHandler 原理一样,只不过它是用来处理出站数据的。
-
ChannelPipeline 提供了 ChannelHandler 链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过 pipeline 中的一系列 ChannelOutboundHandler,并被这些 Handler 处理,反之则称为入站的。如下图所示:
2 编码解码器
- 当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
- Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler 或者ChannelOutboundHandle r接口。在这些类中,channelRead 方法已经被重写了。以入站为例,对于每个从入站 Channel 读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode() 方法进行解码,并将已经解码的字节转发给 ChannelPipeline 中的下一个ChannelInboundHandler。
3 解码器-ByteToMessageDecoder
- 关系继承图如下。
- 由于不可能知道远程节点是否会一次性发送一个完整的信息,tcp有可能出现粘包拆包的问题,这个类会对入站数据进行缓冲,直到它准备好被处理。
4 Netty的handler链的调用机制
原理图如下:
ChannelPipeline.java
I/O Request
via {@link Channel} or
{@link ChannelHandlerContext}
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
使用自定义的编码器和解码器来说明 Netty 的 handler 调用机制
- 客户端发送long -> 服务器
- 服务端发送long -> 客户端
服务端:
package bin.netty.inandoutbound;
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.ByteToMessageDecoder;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.util.List;
/**
* Netty 调用链机制
*
* @author liyibin
* @date 2021-06-27
*/
public class InAndOutBoundServer {
public static void main(String[] args) throws Exception {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 出站处理器
pipeline.addLast(new MyLongMessageEncoder());
// 自定义解码器
pipeline.addLast("decoder", new MyLongMessageDecoder());
// 使用 ReplayingDecoder 实现,不需使用 in.readableBytes() >= 8 进行判断
// pipeline.addLast("decoder", new MyLongMessageDecoder2());
pipeline.addLast(new SimpleChannelInboundHandler<Long>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("读取的数据:" + msg + "L");
// 响应数据
ctx.writeAndFlush(987654321L);
}
});
}
});
ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("listen on 9999");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
MyLongMessageEncoder:
package bin.netty.inandoutbound;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import java.util.List;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyLongMessageEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("MyLongMessageEncoder encode 被调用");
out.writeLong(msg);
}
}
MyLongMessageDecoder:
package bin.netty.inandoutbound;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyLongMessageDecoder extends ByteToMessageDecoder {
/**
* 改方法会被循环调用,直到缓冲区中的数据被读取完成
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 如果当前缓冲区的数据够 8 字节,就读取为 long 型数据,并放到 out 集合中,
// 然后会将改数据传递到后面 handler 中处理
if (in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
MyLongMessageDecoder2:
package bin.netty.inandoutbound;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
/**
* @author liyibin
* @date 2021-06-27
*/
public class MyLongMessageDecoder2 extends ReplayingDecoder<Void> {
/**
* 改方法会被循环调用,直到缓冲区中的数据被读取完成
* 使用 ReplayingDecoder 实现,不需使用 in.readableBytes() >= 8 进行判断
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
out.add(in.readLong());
}
}
客户端:
package bin.netty.inandoutbound;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
/**
* @author liyibin
* @date 2021-06-27
*/
public class InAndOutBoundClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//出站处理器 - 编码器
pipeline.addLast(new MyLongMessageEncoder());
// 自定义解码器
pipeline.addLast("decoder", new MyLongMessageDecoder());
// 使用 ReplayingDecoder 实现,不需使用 in.readableBytes() >= 8 进行判断
// pipeline.addLast("decoder", new MyLongMessageDecoder2());
pipeline.addLast(new SimpleChannelInboundHandler<Long>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服务器响应消息:" + msg);
}
/**
* 通道连接上开始处理
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("开始向服务器写数据");
// 写入消息
ctx.writeAndFlush(12345679L);
// 这样也会被解码
// ctx.writeAndFlush(Unpooled.copiedBuffer("1234567887654321", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
channelFuture.addListener(cf -> {
if (cf.isSuccess()) {
System.out.println("connect to server");
}
});
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
结论:
- 不论解码器 handler 还是编码器 handler 即接收的消息类型必须与待处理的消息类型一致,否则该handler不会被执行。
- 在解码器进行数据解码时,需要判断缓存区(ByteBuf)的数据是否足够 ,否则接收到的结果会期望结果可能不一致。
5 解码器-ReplayingDecoder
-
public abstract class ReplayingDecoder
extends ByteToMessageDecoder -
ReplayingDecoder 扩展了 ByteToMessageDecoder 类,使用这个类,我们不必调用readableBytes() 方法。参数 S 指定了用户状态管理的类型,其中Void代表不需要状态管理。
-
下面代码使用 ReplayingDecoder 对上面的案例进行简化。
-
ReplayingDecoder 局限性。
并不是所有的 ByteBuf 操作都被支持,如果调用了一个不被支持的方法,将会抛出一个 UnsupportedOperationException。
ReplayingDecoder 在某些情况下可能稍慢于 ByteToMessageDecoder,例如网络缓慢并且消息格式复杂时,消息会被拆成了多个碎片,速度变慢。
6 其它编解码器
其它解码器
- LineBasedFrameDecoder:这个类在Netty内部也有使用,它使用行尾控制字符(\n或者\r\n)作为分隔符来解析数据。
- DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分隔符。
- HttpObjectDecoder:一个HTTP数据的解码器。
- LengthFieldBasedFrameDecoder:通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息。
其它编码器:和编码器一般是对应的。
7 Log4j 整合到Netty
- 在Maven 中添加对Log4j的依赖 在 pom.xml。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
- 配置 Log4j , 在 resources/log4j.properties。
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %C{1} - %m%n