TCP以字节流的方式进行数据传输,上层应用协议为了对消息进行区分,往往采用如下4种方式。
- 消息长度固定:累计读取到固定长度为LENGTH之后就认为读取到了一个完整的消息。然后将计数器复位,重新开始读下一个数据报文。
- 回车换行符作为消息结束符:在文本协议中应用比较广泛。
- 将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符。
- 通过在消息头中定义长度字段来标示消息的总长度。
netty中针对这四种场景均有对应的解码器作为解决方案。
本问我们继续介绍DelimiterBasedFrameDecoder和FixedLengthFrameDecoder分别来解决以特殊分隔符作为消息的结束标志的解码和定长消息的解码。他们均能解决TCP导致的黏包读半包问题。
1.DelimiterBasedFrameDecoder应用开发
我们以Echo服务器来演示DelimiterBasedFrameDecoder自动完成以分隔符作为码流结束标识消息结束的解码。
EchoServer收到EchoClient的请求消息后,将其打印出来,然后将原始消息返回客户端。消息以”$_” 作为分隔符。
服务端
服务端主要加了DelimiterBasedFrameDecoder,并在构造器中配置消息的最大长度是1024,并设置行分隔符是”$_”。
package netty.quanwei.p5;
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;
/**
* Created by louyuting on 16/11/27.
*/
public class EchoServer {
private final int port;//定义服务器端监听的端口
/** 构造函数中传入参数 **/
public EchoServer(int port){
this.port = port;
}
/** 启动服务器 **/
public void start() throws Exception{
//县城组
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
//创建一个serverbootstrap实例
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)//指定使用一个NIO传输Channel
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
//在channel的ChannelPipeline中加入EchoServerHandler到最后
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new EchoServerHandler());
}
});
//异步的绑定服务器,sync()一直等到绑定完成.
ChannelFuture future = serverBootstrap.bind(this.port).sync();
System.out.println(EchoServer.class.getName()+" started and listen on '"+ future.channel().localAddress());
future.channel().closeFuture().sync();//获得这个channel的CloseFuture,阻塞当前线程直到关闭操作完成
} finally {
boss.shutdownGracefully().sync();//关闭group,释放所有的资源
worker.shutdownGracefully().sync();//关闭group,释放所有的资源
}
}
/**
* main
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
new EchoServer(8000).start();
}
}
handler:
package netty.quanwei.p5;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* Created by louyuting on 16/11/27.
* 服务器端向客户端传输数据......
*
*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter{
private int counter=0;
/**
* 每次收到消息的时候被调用;
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("this is:"+ (++counter) +" time." + " Server received: " + body);
body = body + "$_";
ByteBuf e