文章目录
TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,一般采用如下4种方式:
1.消息长度固定,累计读取到消息长度总和为定长Len的报文之后即认为是读取到了一个完整的消息。计数器归位,重新读取。
2. 将回车换行符作为消息结束符。
3. 将特殊的分隔符作为消息分隔符,回车换行符是他的一种。
4. 通过在消息头定义长度字段来标识消息总长度。
LineBasedframeDecoder属于第二种,今天我们要说的DelimiterBasedFrameDecoder和FixedLengthFrameDecoder属于第三种和第一种。DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题,FixedLengthFrameDecoder用来解决定长消息的粘包问题。
一、DelimiterBasedFrameDecoder
服务端
1、EchoServer
/**
* @author shuliangzhao
* @Title: EchoServer
* @ProjectName design-parent
* @Description: TODO
* @date 2019/8/21 20:53
*/
public class EchoServer {
public void start(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new EchoServerChannelHandler());
try {
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new EchoServer().start(8080);
}
}
2、EchoServerChannelHandler
/**
* @author shuliangzhao
* @Title: EchoServerChannelHandler
* @ProjectName design-parent
* @Description: TODO
* @date 2019/8/21 20:59
*/
public class EchoServerChannelHandler extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
ByteBuf byteBuf = Unpooled.copiedBuffer("\t".getBytes());
pipeline.addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new EchoServerHandler());
}
}
3、EchoServerHandler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
private int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("server reciver oder:" + body + ";this counter is:" + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
客户端
1、EchoClient
public class EchoClient {
public void start(String address,int port) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new EchoClientChannelHandler());
try {
ChannelFuture future = bootstrap.connect(address,port).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new EchoClient().start("127.0.0.1",8080);
}
}
2、EchoClientChannelHandler
public class EchoClientChannelHandler extends ChannelInitializer<SocketChannel> {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
/*
* 这个地方的 必须和服务端对应上。否则无法正常解码和编码
*/
ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
pipeline.addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
pipeline.addLast( new StringDecoder());
pipeline.addLast( new StringEncoder());
// 客户端的逻辑
pipeline.addLast( new EchoClientHandler());
}
}
3、EchoClientHandler
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private byte[] req;
private int counter;
public EchoClientHandler() {
req = ("There are moments in life when you miss\t" +
"someone so much that you just want to pick\t" +
"them from your dreams and hug them for real!\t" +
"Dream what you want to dream;go where you want\t" +
"to go be what you want to be,because you have only\t" +
"one life and one chance to do all the things you want to do\t").getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("client reciver is :" + body + "this is counter:" + ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
运行结果:
这里我们添加DelimiterBasedFrameDecoder解码器并且手动指定消息分隔符为:”\t”。我们可以看一下DelimiterBasedFrameDecoder的构造方法:
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {
this(maxFrameLength, stripDelimiter, true, delimiter);
}
maxFrameLength:解码的帧的最大长度
stripDelimiter:解码时是否去掉分隔符
failFast:为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
delimiter:分隔符
二、FixedLengthFrameDecoder
FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码。
用法如下:
pipeline.addLast(new FixedLengthFrameDecoder(23));//参数为一次接受的数据长度。