基本说明
Netty的组件设计:Netty主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannelInboundHandler
接口(或ChannelInboundHandlerAdapter
),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。业务逻辑通常写在一个或者多个ChannelInboundHandler
中。ChannleOutboundHandler
原理一样,只是它是用来处理出站数据的。
ChannelPipeline
提供了ChannleHandler
链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务器端的,那么我们称这些事件是出站的,即客户端发送给服务器端的数据会通过pipeline中的一系列ChannelOutboundHandler
,并被这些Handler处理,反之如果事件的运动方向是从服务器端到客户端的则称为入站。
编解码器
当Netty发送或者接收一个消息时,就会发生一次数据转换。入站消息会被解码,从二进制字节流转为对象等格式;如果是出站消息,则业务数据则会被编码为二进制字节流。
Netty提供了一系列实用的编解码器,他们实现了ChannelInboundHandler
或者ChannelOutboundHandler
接口。这些编解码器的实现类中,channelRead
方法普遍被重写,以编解码。
以入站为例,channelRead
方法会被先调用,随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
解码器接口ByteToMessageDecoder
、编码器接口MessageToByteEncoder
,或者直接实现MessageToMessageCodec
包含了编解码。当需要自定义编解码器时,只需要实现接口即可。
自定义解码器
例子:
public class ToIntegerDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannleHandlerContext ctx,ByteBuf in,List<Object> out) throws Exception{
if(in.readableBytes()>=4){
out.add(in.readInt());
}
}
}
说明:
每次入站从ByteBuf中读取4个字节(因为int是占4个字节),将其解码为一个int,然后将它添加到下一个List中。当没有更多元素可以被添加到List中时,list的内容会被发送给下一个ChannleInboundHandler。这种情况下,判断字节数是否>=4是必要的,否则会出现粘包拆包问题
下面以一个例子来说明Netty中handler链的调用顺序
例子要求:
- 客户端发送long给服务器端
- 服务器端发送long给客户端
服务器端代码
package com.wojiushiwo.codec;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class NettyCustomCodecServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encoder",new LongEncoder());
pipeline.addLast("decoder",new LongDecoder());
pipeline.addLast(new CustomCodecServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8091).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//handler
package com.wojiushiwo.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class CustomCodecServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("调用了CustomCodecServerHandler#channelRead0");
System.out.println("from client:" + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("调用了CustomCodecServerHandler#channelReadComplete");
ctx.writeAndFlush(123456789L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端代码
package com.wojiushiwo.codec;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class NettyCustomCodecClient {
public static void main(String[] args) {
EventLoopGroup 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("encoder", new LongEncoder());
pipeline.addLast("decoder", new LongDecoder());
pipeline.addLast(new CustomCodecClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8091).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
//handler
package com.wojiushiwo.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class CustomCodecClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端CustomCodecClientHandler#channelActive");
ctx.writeAndFlush(1999999L);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("客户端CustomCodecClientHandler#channelRead0");
System.out.println("from server:" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
自定义编解码器代码
//编码器
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* Created by myk
* 2020/1/29 下午7:04
*/
public class LongEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("调用编码器LongEncoder#encode");
out.writeLong(msg);
}
}
//解码器
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* Created by myk
* 2020/1/29 下午7:03
*/
public class LongDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("调用LongDecoder#decode");
//long 8个字节
if (in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
服务器端输出:
调用LongDecoder#decode
调用了CustomCodecServerHandler#channelRead0
from client:1999999
调用了CustomCodecServerHandler#channelReadComplete
调用编码器LongEncoder#encode
客户端输出:
客户端CustomCodecClientHandler#channelActive
调用编码器LongEncoder#encode
调用LongDecoder#decode
客户端CustomCodecClientHandler#channelRead0
from server:123456789
通过打印字符串到控制台可以发现调用顺序:
客户端ChannelActive=>客户端LongEncoder#encode=>服务器端LongDecoder#decode=>CustomCodecServerHandler#channelRead0=>CustomCodecServerHandler#channelReadComplete=>服务器端LongEncoder#encode=>客户端LongDecoder#decode=>CustomCodecClientHandler#channelRead0
总结:
- 不论解码器handler还是编码器handler,接收的消息类型必须与待处理的消息类型一致,否则该handler不会被执行
- 在解码器进行数据解码时,需要判断缓冲区的数据是否足够,否则接收到的结果可能会与期望结果不一致。
解码器-ReplayingDecoder
ReplayingDecoder
继承了ByteToMessageDecoder
类,使用这个类,我们不需要显式的去判断缓冲区是否足够,该类会自动去做这个事情。泛型S指定了用户状态管理的类型,其中Void表示不需要状态管理。
示例:
使用ReplayingDecoder重构LongDecoder
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
/**
* Created by myk
* 2020/1/29 下午7:47
*/
public class LongDecoder2 extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//不需要判断数据是否足够读取,内部会进行处理判断
out.add(in.readLong());
}
}
ReplayingDecoder优缺点:
优点:使用方便
缺点:
- 并不是所有的ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出
UnsupportOperationException
,比如ReplayingDecoderByteBuf#array()方法 - ReplayingDecoder在某些情况下会慢于ByteToMessageDecoder,如网络缓慢且消息格式复杂时,消息会拆成了多个碎片,速度变慢。
其他编解码器
- LineBasedFrameDecoder:这个类在Netty内部有使用,它使用行尾控制符(\n或者\r\n)作为分隔符来解析数据
- DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分割符
- HttpObjectDecoder:Http数据的解码器
- LengthFieldBasedFrameDecoder:通过指定长度来标识整包信息,这样就可以自动的处理粘包和半包问题
- ObjectEncoder:与对象有个的编码器
- …