TCP    以流的方式进行数据传输上层的应用协议为了对消息进行区分,往往采用如下4种方式。

(1)消息长度固定,累计读取到长度总和为定长LEN 的报文后,就认为读取到了一个完整的消息,将计数器置位,重新开始读取下一个数据报;
(2)将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛:
(3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符:
(4)通过在消息头中定义长度字段来标识消息的总长度。

Netty对上面四种应用做了统一的抽象提供了4种解码器来解决对应的问题,使用起来非常方便。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑TCP的粘包和拆包。

第4章我们介绍了如何利用LineBasedFrameDecoder解决TCP的粘包问题,本章我们继续学习另外两种实用的解码器一一DelimiterBasedFrameDecoder和FixedLengthFrameDecoer,前者可以自动完成以分隔符做结束标志的消息的解码,后者可以自动完成对定长消息的解码,它们都能解决TCP粘包/拆包导致的读半包问题。
本章主要内容包括:

1.DelimiterBasedFrameDecoder服务端开发
2.DelimiterBasedFrameDecoder客户端开发
3.运行DelimiterBasedFrameDecoder服务端和客户端
4.FixedLengthFrameDecoer服务端开发
5.通过telnet命令行调试FixedLengtllFrameDecoder服务端


5.1 DelimiterBasedFrameDecoder应用开发

通过对DelimiterBasedFrameDecoder的使用,我们可以自动完成以分隔符作为码流结束标识的消息的解码,下面通过一个演示程序来学习下如何DelimiterBasedFrameDecoder进行开发。

演示程序以经典的Echo服务为例。EchoServer接收到EchoClient的请求消息后,将其打印出来,然后将原始消息返回给客户端,消息以“$”作为分隔符。

5.1.1 DelimiterBasedFrameDecoder  服务端开发
下面我们直接看EchoServer的源代码:

EchoServer服务端EchoServer

package lqy4_delimiter_101;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_"
.getBytes());
ch.pipeline().addLast(
new DelimiterBasedFrameDecoder(1024,
delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoServerHandler());
}
}); // 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync(); // 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
}

我们重点看3741行,首先创建分隔符缓冲对象ByteBuf,本例程中使用$作为分隔符。第40行,创建DelimiterBasedFrameDecoder对象,将其加入到ChannelPipeline中。DelimiterBasedFrameDecoder有多个构造方法,这里我们传递两个参数,第一个1024表示单条消息的最大长度,当达到该长度后仍然没有查找到分隔符,就抛出TooLongFrameException异常,防止由于异常码流缺失分隔符导致的内存溢出,这是Netty解码器的可靠
性保护:第二个参数就是分隔符缓冲对象。

下面继续看EcboServerHandler的实现。

package lqy4_delimiter_101;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; /**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
@Sharable
public class EchoServerHandler extends ChannelHandlerAdapter {
int counter = 0; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String) msg;
System.out.println("This is " + ++counter + " times receive client : ["
+ body + "]");
body += "$_";
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 发生异常,关闭链路
}
}

第21~23行直接将接收的消息打印出来,由于DelimiterBasedFrameDecoder自动对请求消息进行了解码,后续的ChannelHandler接收到的msg对象就是个完整的消息包;第二个ChannelHandler是StringDecoder,它将ByteBuf解码成字符串对象:第三个EchoServerHandler接收到的msg消息就是解码后的字符串对象。
由于我们设置DelimiterBasedFrameDecoder过滤掉了分隔符,所以,返回给客户端时需要在请求消息尾部拼接分隔符“$_”,最后创建ByteBuf,将原始消息重新返回给客户端。

5.1.2    DelimiterBasedFrameDecoder客户端开发

首先看下EchoClient的实现。

EchoClient客户端EchoClient

 package lqy4_delimiter_101;

 import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder; /**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoClient { public void connect(int port, String host) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_"
.getBytes());
ch.pipeline().addLast(
new DelimiterBasedFrameDecoder(1024,
delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoClientHandler());
}
}); // 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync(); // 当代客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
} /**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoClient().connect(port, "127.0.0.1");
}
}

与服务端类似,分别将DelimiterBasedFrameDecoder和StringDecoder添加到客户端ChannelPipeline中,最后添加客户端1/0事件处理类EchoClientHandler,下面继续看EchoClientHandler的实现。

EchoClient客尸端    EchoClientHandler

package lqy4_delimiter_101;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoClientHandler extends ChannelHandlerAdapter {
private int counter; static final String ECHO_REQ = "Hi, Lilinfeng. Welcome to Netty.$_";
/**
* Creates a client-side handler.
*/
public EchoClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
// ByteBuf buf = UnpooledByteBufAllocator.DEFAULT.buffer(ECHO_REQ
// .getBytes().length);
// buf.writeBytes(ECHO_REQ.getBytes());
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("This is " + ++counter + " times receive server : ["
+ msg + "]");
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

第25~26行在TCP链路建立成功之后循环发送请求消息给服务端,第32~33行打印接收到的服务端应答消息同时进行计数。

5.1.3    运行DelimiterBasedFrameDecoder服务端和客户端

服务端运行结果如下。

 This is 1 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 2 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 3 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 4 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 5 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 6 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 7 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 8 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 9 times receive client : [Hi, Lilinfeng. Welcome to Netty.]
This is 10 times receive client : [Hi, Lilinfeng. Welcome to Netty.]

客户端运行结果如下。

 This is 1 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 2 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 3 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 4 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 5 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 6 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 7 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 8 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 9 times receive server : [Hi, Lilinfeng. Welcome to Netty.]
This is 10 times receive server : [Hi, Lilinfeng. Welcome to Netty.]

服务端成功接收到了客户端发送的10条“Hi,Lilinfeng.WelcometoNetty.”
请求消息,客户端成功接收到了服务端返回的10条“Hi,Lilinfe口g.WelcometoNetty.”应答消息。测试结果表明使用Delin1iterBasedFrameDecoder可以自动对采用分隔符做码流结束标识的消息进行解码。

本例程运行10次的原因是模拟TCP粘包/拆包,在笔者的机器上,连续发送10条Echo请求消息会发生粘包,如果没有DelimiterBasedFrameDecoder解码器的处理,服务端和客户端程序都将运行失败。

下面我们将服务端的DelimiterBasedFrameDecoder注释掉,最终代码如下
aaarticlea/png;base64,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" alt="" />

服务端结果

This is 1 times receive client : [Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_Hi, Lilinfeng. Welcome to Netty.$_]

由于没有分隔符解码器,导致服务端一次读取了客户端发送的所有消息,这就是典型的没有考虑TCP粘包导致的问题。

5.2    FixedLengthFrameDecoder应用开发

FixedLengtl1FrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包问题,非常实用。下面我们通过一个应用实例对其用法进行讲解。

5.2.1  FixedlengthFrameDecoder服务端开发

在服务端的ChannelPipeline中新增FixedLengthFrameDecoder,长度设置为20,然后再依次增加字符串解码器和EchoServerHandler,代码如下。

EcboServer服务端    EchoServer

package lqy5_fixlengthframe_108;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new FixedLengthFrameDecoder(20));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoServerHandler());
}
}); // 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync(); // 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
new EchoServer().bind(port);
}
}

EchoServerHandler 的功能 比较简单 ,直接将 读取到的消息打印 出来 ,代码如下 。

EchoServer  服务端    EchoServerHandler

 package lqy5_fixlengthframe_108;

 import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; /**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
@Sharable
public class EchoServerHandler extends ChannelHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("Receive client : [" + msg + "]");
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();// 发生异常,关闭链路
}
}

利用FixedLengthFrameDecoder解码器,无论一次接收到多少数据报,它都会按照构造函数中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。

下面的章节我们通过telnet命令行来测试EchoServer服务端,看它能否按照预期进行工作。

5.2.1 利 用 telnet 命令行测试 EchoServer 服务端

5.3    总结

本章我们学习了两个非常实用的解码器:DelimiterBasedFrameDecoder和FixedLengthFrameDecoder。
DelimiterBasedFrameDecoder用于对使用分隔符结尾的消息进行自动解码,FixedLengthFrameDecoder用于对固定长度的消息进行自动解,码。有了上述两种解码器,再结合其他的解码器,如字符串解码器等,可以轻松地完成对很多消息的自动解码,而且不再需要考虑TCP粘包/拆包导致的读半包问题,极大地提升了开发效率。
应用DelimiterBasedFrameDecoder和FixedLengthFrameDecoder进行开发非常简单,在绝大数情况下,只要将DelimiterBasedFrameDecoder或FixedLengthFran1eDecoder添加到对应ChanneIPipeline的起始位即可。
熟悉了Netty的NIO基础应用开发之后,从第三部分开始,我们继续学习编解码技术。在了解编解码基础知识之后,继续学习Netty内置的编解码框架的使用,例如Java序列化、二进制编解码、谷歌的protobuf和JBoss的Marshalling序列化框架。

05-11 18:30