tcp以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用如下4种方式:
1、消息固定长度,累计读取到长度一定的报文段后,就读取下一个报文段。
2、将回车换行符做为消息结束符,例如ftp协议,这种方式在文本协议中应用比较广泛。
3、将特殊的分割符作为消息结束标志,回车换行就是一种特殊的结束分隔符;
4、通过在消息头中定义长度字段来标识消息的总长度。
Netty对上面的4种应用做了统一的抽象,提供了4种解码器来解决对应的问题,有了这些解码器,我们不用自己对读取的报文进行人工解码,也不需要考虑tcp的拆包和粘包问题。上一节中我们介绍了LineBaseFrameDecoder解决tcp的粘包问题,这一节将介绍另外两种使用的解码器--------DelimterBasedFrameDecoder和FixedLengthFrameDecoder,前者可以自动完成以分割符做结束标志的消息解码,后者可以完成对消息定长的解码,他们都能解决tcp粘包和拆包的读半包问题。
DelimterBasedFrameDecoder的使用,指定分割符来进行消息的分割,其代码如下:
public class EchoServer {
public void bind(int port) throws Exception {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boosGroup, workerGroup).childOption(ChannelOption.TCP_NODELAY, true).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected 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 {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
}
}
new EchoServer().bind(port);
}
}
如上使用了$_进行消息的分割,EchoServerHandler如下:
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) throws Exception {
ctx.close();
}
}
EchoClient如下:
public class EchoClient {
public void connect(int port, String host) throws Exception {
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
protected 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 {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
}
}
new EchoClient().connect(port, "127.0.0.1");
}
}
其clientHandler如下:
public class EchoClientHandler extends ChannelHandlerAdapter {
private static final String ECHO_REQ = "Hi ,zouxiaohu.Welcome to Netty.$_";
private int counter;
@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 channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户端运行如下,能正常的读取tcp包:
this is 1 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 2 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 3 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 4 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 5 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 6 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 7 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 8 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 9 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
this is 10 times receive server :[ Hi ,zouxiaohu.Welcome to Netty.]
FixedLengFrameDecoder的应用
FixedLengFrameDecoder对固定长度的消息进行分割。使用如下:
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new EchoServerHandler2());
}
指定固定的长度为20。
对应的ServerHandler则读取如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("this is " + ++counter + " times receive client : [" + body + " ]");
}
此时运行Server,然后通过telnet命令进行发送指令回看到,发送的指令是以固定长度20的进行线上的。不足20的不会进行显示。