1、Netty的主要组件有:Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipeline等。
- ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如:实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),可以接收入站事件和数据,这些数据将被业务逻辑处理;当给客户端回送响应时,也可以通过ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或多个ChannelInboundHandler中。
- ChannelOutboundHandler与之类似,只不过是用来处理出站数据的。
- ChannelPipeline提供了ChannelHandler链的容器(pipeline.addLast()可以将一系列的handler以链表的形式添加),以客户端应用程序为例,如果事件运动方向为客户端->服务器,称之为“出站”,即客户端发送给服务器的数据通过pipeline中的一系列ChannelOutboundHandler,并被这些handler处理。反之则称为“入站”。
【注】“出站”和“入站”不是绝对概念,而是相对的。若以客户端为观察主体,则客户端->服务器称为“出站”,服务器->客户端称为“入站”;若以服务器为观测主体,则与之相反。简而言之,如果把网络链路看作一条公路,客户端和服务器分别为公路上的收费站,进入收费站的行为统称为“入站”,离开收费站的行为统称为“出站”。
2、编解码器
- Netty发送或接收一个消息时,会发生一次数据的转换:入站消息会被解码,二进制->其他格式 ||出站消息会被编码,其他格式->二进制。
- Netty提供了一系列编码解码器,都实现了ChannelInboundHandler / ChannelOutboundHandler接口。以入站消息为例,对于每个从channel中读取的信息,重写并调用了channelRead方法,随后,它将调用解码器提供的decode()方法进行解码,并将解码后的字节发送给ChannelPipeline中的下一个ChannelInboundHandler。
【注】消息入站后,会经过ChannelPipeline中的一系列ChannelHandler处理,这些handler中有Netty已经实现的,也有我们重新实现的自定义handler,但它们都需要实现ChannelInboundHandler接口;即消息入站后所经过的handler链是由一系列ChannelInboundHandler组成的,其中第一个经过的handler就是解码器Decoder;消息出站与入站类似,但消息出站需要经过一系列ChannelOutboundHandler的实现类,最后一个经过的handler是编码器Encoder。
2-1、解码器——ByteToMessageDecoder
- 由于不知道远程节点是否会发送一个完整的信息,TCP可能出现粘包和拆包的问题。ByteToMessageDecoder的作用就是对入站的数据进行缓冲,直至数据准备好被处理。
- ByteToMessageDecoder实例分析
public class ToIntgerDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception{
if (in.readableBytes() >= 4) {
out.add(in.readint());
}
}
}在此实例中,假设通过Socket发送了8字节数据,每次入站从ByteBuf中读取个4字节,将其解码为一个int,并加入一个List中。当没有更多的元素可以被添加到该List中时,代表此次发送的数据已发送完成,List中的所有内容会被发送给下一个ChannelInboundHandler。Int在被添加到List中时,会被自动装箱为Intger,调用readInt()方法前必须验证所输入的ByteBuf是否有足够的数据。
2-2、Netty的handler链调用机制
2-2-1、实例要求
- 使用自定义的编码解码器
- 客户端可以发送一个Long类型的数据给服务器;服务器也可以发送一个Long类型的数据给客户端
2-2-2、实现
- 客户端和服务器主程序负责绑定端口,进行连接;
- 客户端和服务器都实现一个自定义的初始化类Initializer,在初始化类中将编解码器和自定义的handler链式添加pipeline中;
- 自定义编解码器需要实现ByteToMessageDecoder和MessageToByteEncoder。
自定义编码器——myLongToByteEncoder.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class myLongToByteEncoder extends MessageToByteEncoder<Long> {
//编码的方法
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("Encoder被调用");
System.out.println("msg = "+msg);
out.writeLong(msg);
}
}
自定义解码器——myByteToLongDecoder.java
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class myByteToLongDecoder extends ByteToMessageDecoder {
/**
*
* decode会根据接收的数据,被调用多次,直到确定没有新的元素添加到list,或者时ByteBuf没有更多的可读字节为止
* 如果list out不为空,就会将list的内容传递给下一个channelInboundHandler处理,该方法也会被调用多次
*
* @param ctx 上下文对象
* @param in 入站的ByteBuf
* @param out List集合,将解码后的数据传给下一个handler处理
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// long——>8个字节,需要判断有8个字节,才能读取一个long
if (in.readableBytes()>=8){
out.add(in.readLong());
}
System.out.println("Decoder被调用");
}
}
服务器端——myServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class myServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new mySeverInitializer()); //自己定义的初始化类
ChannelFuture channelFuture = serverBootstrap.bind(7001).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
客户端——myClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class myClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new myClientInitializer()); //自定义一个初始化对象
ChannelFuture channelFuture = bootstrap.connect("localhost", 7001).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
服务器端初始化类——mySeverInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class mySeverInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//入站的handler进行解码 myByteToLongDecoder
pipeline.addLast(new myByteToLongDecoder());
pipeline.addLast(new myLongToByteEncoder());
//自定义handler处理业务逻辑
pipeline.addLast(new myServerHandler());
}
}
客户端初始化类——myClientInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
public class myClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入一个出站的handler,对数据进行编码
pipeline.addLast(new myLongToByteEncoder());
//这是一个入站的解码器
pipeline.addLast(new myByteToLongDecoder());
//加入一个自定义handler,处理业务
pipeline.addLast(new myClientHandler());
}
}
服务器自定义handler——myServerHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class myServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("从客户端"+ctx.channel().remoteAddress() + " 读取到Long "+ msg);
//给客户端回送一个long
ctx.writeAndFlush(987654L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端自定义handler——myClientHandler.java
package com.SF.Netty.InBoundAndOutBound;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class myClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服务器的ip = "+ctx.channel().remoteAddress());
System.out.println("收到服务器数据 = "+msg);
}
//重写channelActive,发送数据
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("myClientHandler 发送数据");
ctx.writeAndFlush(12345678L); //发送一个long
//该处理器的前一个handler 是 myLongToByteEncoder
//myLongToByteEncoder 的父类是 MessageToByteEncoder
//父类 MessageToByteEncoder
/**
* public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
* ByteBuf buf = null;
* try {
* if (acceptOutboundMessage(msg)) {
* //判断当前 msg是否是该被处理的类型,如果是就处理,否则跳过encode()
* @SuppressWarnings("unchecked")
* I cast = (I) msg;
* buf = allocateBuffer(ctx, cast, preferDirect);
* try {
* encode(ctx, cast, buf);
* } finally {
* ReferenceCountUtil.release(cast);
* }
*
* if (buf.isReadable()) {
* ctx.write(buf, promise);
* } else {
* buf.release();
* ctx.write(Unpooled.EMPTY_BUFFER, promise);
* }
* buf = null;
* } else {
* ctx.write(msg, promise);
* }
*/
//因此我们在编写encoder时,要注意传入的数据类型和处理的数据类型一致
// ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd", CharsetUtil.UTF_8));
}
}
2-2-3、结论
- 不论是解码器handler还是编码器handler,接收的消息类型必须与待处理的消息类型一致,否则该handler不会执行;
- 在解码器进行数据解码时,需要判断缓冲区ByteBuf的数据是否足够,否则收到的结果可能和期望结果不一致。
2-3、Netty其他编解码器
2-3-1、解码器-ReplayingDecoder
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class myByteToLongDecoder2 extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("Decoder2被调用");
//在 ReplayingDecoder 不需要判断数据是否足够读取,内部会判断处理
out.add(in.readLong());
}
}
声明: public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
解释:ReplayingDecoder扩展了ByteToMessageDecoder类,使用这个类,我们不必调用readableBytes()方法,参数S指定了用户状态管理的类型,其中,Void代表不需要状态管理。
局限性:不是所有的ByteBuf操作都支持,如果调用了一个不被支持的方法,将会抛出UnsupportedOperationException。ReplayingDecoder在网络缓慢且消息格式复杂时,消息会被拆成多个碎片,导致速度变慢。
2-3-2、其他解码器
- LineBasedFrameDecoder:它使用行尾控制字符(\n或\r\n)作为分割符来解析数据;
- DelimiterBasedFrameDecoder:使用自定义的特殊字符作为分隔符;
- HttpObjectDecoder:一个HTTP数据的解码器;
- LengthFieldBasedFrameDecoder:通过指定长度来标识整包信息,这样就可以自动的处理粘包和半包信息
3、log4j整合到Netty
3-1、log4j概述
log4j是Apache提供的一个功能强大的日志组件,通过log4j,我们可以指定日志的输出格式、输出位置和日志的级别等内容,使用log4j.properties(键=值)作为配置文件时,主要使用方法为:
- 配置根Logger,负责处理日志记录的大部分操作,语法为:log4j.rootLogger = [ level ] , appenderName, appenderName, …。其中,level 是日志记录的优先级,建议使用ERROR、WARN、INFO、DEBUG四个优先级;appenderName就是指定日志信息输出到哪个地方。可同时指定多个输出目的地。
- 配置日志信息输出目的地 Appender,负责控制日志记录操作的输出。语法为:log4j.appender.appenderName = fully.qualified.name.of.appender.class,其中appenderName与第一步起得appenderName一致,log4j提供的appender有org.apache.log4j.ConsoleAppender(控制台);..FileAppender(文件);..DailyRollingFileAppender(每天产生一个日志文件);..RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件);..WriterAppender(将日志信息以流格式发送到任意指定的地方)等;
- 配置日志信息的格式(布局)Layout,负责格式化Appender的输出。语法为:log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class,log4j提供的layout有HTMLLayout(以HTML表格形式布局);PatternLayout(可以灵活地指定布局模式);SimpleLayout(包含日志信息的级别和信息字符串);TTCCLayout(包含日志产生的时间、线程、类别等等信息)
3-2、配置log4j
在pom.xml中配置log4j的依赖
<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> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> </dependency> <!--日志end-->
配置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