Netty 分隔符和定长解码器的应用
- 理论说明
- LineBasedFrameDecoder 开发
- 大概流程
- 代码展示
- netty 依赖
- EchoServer 服务端启动类
- EchoServerHandler
- EchoClient
- EchoClientHandler
- 结果打印
- 客户端打印
- 服务端打印
- FixedLengthFrameDecoder 开发
- 代码展示
- EchoServer 服务端启动类
- EchoFixServerHandler 业务处理类
- 客户端
- 效果展示
- 服务端打印
- 总结
理论说明
TCP 以流的方式进行数据传输,长层的应用协议为了对消息进行区分,往往采用如下四种方式
(1)消息定长固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息:将计数器置位,重新开始读取下一个数据报
(2)将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛。
(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束标志。
(4)通过在消息头中定义长度字段来标识消息的总长度
Netty对上述四种应用都做了统一的抽象,提供了四种解码器来解决对应的问题,使用起来非常方便。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑粘包和拆包的问题。
在 【三】Netty 解决粘包和拆包问题 我们介绍了利用LineBasedFrameDecoder 解决了TCP的粘包问题。本节我们继续学习下另外两种实用的解码器 DelimiterBasedFrameDecoder和 FixedLengthFrameDecoder,前者可以自动完成以分隔符做结束标志的消息的解码,后者可以自动完成对定长消息的解码,它们都能解决TCP粘包和拆包导致的读半包问题。
LineBasedFrameDecoder 开发
大概流程
EchoServer接受到EchoClient的请求消息后,将其打印出来,然后将原始消息返回给客户端,消息以 $_ 作为分隔符.
代码展示
netty 依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
EchoServer 服务端启动类
public class EchoServer {
public static void main(String[] args) {
new EchoServer().bind(8080);
}
public void bind(int port) {
//配置服务端的NIO线程组
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel)
throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
//添加 DelimiterBasedFrameDecoder 解码器,入参是对应的特殊字符 $_,作为分隔符
//1024 是单条消息的最大长度,当达到最大 长度也没有分隔符时就抛出异常
// 我们可以根据我们自己的需要,选择对应的特殊字符
socketChannel.pipeline().
addLast(new
DelimiterBasedFrameDecoder(
1024, delimiter));
socketChannel.pipeline()
.addLast(new StringDecoder());
/* socketChannel.pipeline()
.addLast(new StringEncoder());*/
socketChannel.pipeline().addLast(
new EchoServerHandler()
);
}
});
//绑定端口,等待同步成功
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("server is started");
//等待服务器监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//优雅退出
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
EchoServerHandler
public class EchoServerHandler extends ChannelHandlerAdapter {
int counter=0;
public void channelRead(ChannelHandlerContext context,Object body){
//String body=(String)msg;
// EchoServer 在 添加 childHandler中,添加了三个 ChannelHandler
// 1.DelimiterBasedFrameDecoder 自动对请求消息进行解码,
// 后续的ChannelHandler接受到的body就是完整的消息包
// 2.StringDecoder 它将ByteBuf 解码成字符串对象
// 3.EchoServerHandler 接受到的 body 对象就是解码后的字符串对象
System.out.println("This is "+ ++counter+ " times receive client : 【"+body+"】");
body+="$_";
//由于我们设置 DelimiterBasedFrameDecoder 过滤掉了分隔符,所以,返回客户端时
//需要将分隔符$_ 拼接上去,最后创建ByteBuf对象返回给客户端。
ByteBuf echo= Unpooled.copiedBuffer(body.toString().getBytes());
context.writeAndFlush(echo);
}
}
EchoClient
public class EchoClient {
public static void main(String[] args) {
new EchoClient().connect("127.0.0.1",8080);
}
public void connect(String host,int port){
//配置客户端NIO线程组
EventLoopGroup loopGroup=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(loopGroup)
.option(ChannelOption.TCP_NODELAY,true)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ByteBuf delimiter= Unpooled.copiedBuffer("$_".getBytes());
//添加 DelimiterBasedFrameDecoder 解码器,入参是对应的特殊字符 $_作为分隔符
//1024 是单条消息的最大长度,当达到最大 长度也没有分隔符时就抛出异常
// 我们可以根据我们自己的需要,选择对应的特殊字符
socketChannel.pipeline()
.addLast(new DelimiterBasedFrameDecoder(
1024,delimiter));
socketChannel.pipeline()
.addLast(new StringDecoder());
/* socketChannel.pipeline()
.addLast(new StringEncoder());*/
socketChannel.pipeline()
.addLast(new EchoClientHandler());
}
});
//进行tcp 链接服务端
ChannelFuture future=bootstrap.connect(host,port).sync();
System.out.println("client is started ....");
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//优雅退出线程组
loopGroup.shutdownGracefully();
}
}
}
EchoClientHandler
public class EchoClientHandler extends ChannelHandlerAdapter {
private int counter;
static final String ECHO_REQ = "Hi ,echo ,welcome to Netty $_";
public void channelActive(ChannelHandlerContext context) {
for (int i = 0; i < 10; i++) {
context.writeAndFlush(
Unpooled.copiedBuffer(ECHO_REQ.getBytes())
);
}
}
public void channelRead(ChannelHandlerContext context,Object obj){
System.out.println("the counter is "+ ++counter+" times receive server 【"+obj+" 】");
}
}
结果打印
客户端打印
服务端打印
FixedLengthFrameDecoder 开发
FixedLengthFrameDecoder 是固定长度解码器,他能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP 粘包/拆包问题。
开发的代码比较简单,我们展示下代码:
代码展示
EchoServer 服务端启动类
public class EchoServer {
public static void main(String[] args) {
new EchoServer().bind(8080);
}
public void bind(int port) {
//配置服务端的NIO线程组
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel)
throws Exception {
//定长解码器
socketChannel.pipeline().
addLast(new
FixedLengthFrameDecoder(
20));
socketChannel.pipeline()
.addLast(new StringDecoder());
/* socketChannel.pipeline()
.addLast(new StringEncoder());*/
socketChannel.pipeline().addLast(
new EchoFixServerHandler()
);
}
});
//绑定端口,等待同步成功
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("server is started");
//等待服务器监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//优雅退出
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
}
EchoFixServerHandler 业务处理类
public class EchoFixServerHandler extends ChannelHandlerAdapter {
public void channelRead(ChannelHandlerContext context,Object msg){
System.out.println("receive client message is 【"+msg+"】");
}
}
客户端
我们采用NetAssist 网络助手来进行测试。
下载地址:https://download.csdn.net/download/echohuangshihuxue/87311946?spm=1001.2014.3001.5503
效果展示
服务端打印
由于我们配置的是定长20个字节,所以发送的内容分为三次来接受打印了。
总结
DelimiterBasedFrameDecoder 解码器用于对使用分隔符结尾的消息进行自动解码,
FixedLengthFrameDecoder 用于对固定长度的消息进行自动解码,有了上述两种解码器,再结合其他解码器,如字符串解码器,就可以轻松的完成对很多消息的自动解码,而且不再考虑TCP粘包和拆包导致的读半包的问题,极大的提升了开发效率。