TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,采用消息长度固定、以回车换行符作为结束标志、特殊分隔符作为结束标志、消息头中定义长度等方式。Netty对于这几种方式做了统一的抽象,分别提供四种解码器解决。
在这里,我使用DelimiterBasedFrameDecoder解码器来解决以分隔符作为结束标志的消息的解码。
PS:FixedLengthFrameDecoder是定长消息的解码。
程序演示echo服务,服务端收到客户端的请求消息后,将其打印出来,并将原消息返回给客户端。程序以“$$”为分隔符。
服务端:
EchoServer类
public class EchoServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
try {
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.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 future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 443;
new EchoServer().bind(port);
}
}
EchoServerHandler类
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
private int count = 0;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String) msg;
System.out.println("This is : " + ++count + " times receive client , body is " + body);
body += "$$";
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@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 {
// 配置客户端NIO 线程组
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap client = new Bootstrap();
try {
client.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 future = client.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 443;
EchoClient client = new EchoClient();
try {
client.connect(port, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
EchoClientHandler类
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private int count;
static final String ECHO_REQ = "hello, welcome to Netty,$$";
@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 channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String) msg;
System.out.println("This is : " + ++count
+ " times receive client , body is " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
运行结果,客户端和服务器都会打印如下的内容,说明使用DelimiterBasedFrameDecoder可以完成以分隔符为结束标志的解码。
This is : 1 times receive client , body is hello, welcome to Netty,
This is : 2 times receive client , body is hello, welcome to Netty,
This is : 3 times receive client , body is hello, welcome to Netty,
This is : 4 times receive client , body is hello, welcome to Netty,
This is : 5 times receive client , body is hello, welcome to Netty,
This is : 6 times receive client , body is hello, welcome to Netty,
This is : 7 times receive client , body is hello, welcome to Netty,
This is : 8 times receive client , body is hello, welcome to Netty,
This is : 9 times receive client , body is hello, welcome to Netty,
This is : 10 times receive client , body is hello, welcome to Netty,
如果想测试一下没有使用分隔符的解码器,其实只需要注释掉服务端一下两条语句即可。
ByteBuf delimiter = Unpooled.copiedBuffer("$$".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
然后服务端的结果为:
This is : 1 times receive client , body is hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$
客户端的结果为:
This is : 1 times receive client , body is hello, welcome to Netty,
This is : 2 times receive client , body is hello, welcome to Netty,
This is : 3 times receive client , body is hello, welcome to Netty,
This is : 4 times receive client , body is hello, welcome to Netty,
This is : 5 times receive client , body is hello, welcome to Netty,
This is : 6 times receive client , body is hello, welcome to Netty,
This is : 7 times receive client , body is hello, welcome to Netty,
This is : 8 times receive client , body is hello, welcome to Netty,
This is : 9 times receive client , body is hello, welcome to Netty,
This is : 10 times receive client , body is hello, welcome to Netty,
This is : 11 times receive client , body is
根据结果可以清楚的看到,没有分隔符解码器导致服务端一次读取了客户端发的所有消息,这也是典型的没有考虑TCP粘包问题导致的。