0 概述
TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用以下四种方式
消息的长度固定,累计读取到长度总和为定长的len的报文之后,都认为读取到了一个完整的消息。
将回车换行符作为消息的结束符,例如FTP协议,这种方式在文本协议中应用的比较广泛。
将特殊的分隔符作为消息的结束标志,回车换行符本身就是一种特殊的符号。
通过在消息头中定义长度字段来标识消息的总长度。
netty 对上面的几种做了统一 的抽象,提供了4种解码器来解决对应的问题。
1 特定分割符解码
netty 提供了DelimiterBasedFrameDecoder作为分割符解码器。
公共类
public class DelimiterConstant {
//分割符
public final static String DELIMITER_STR="$*";
public final static ByteBuf DELIMITER_BUF = Unpooled.copiedBuffer(DELIMITER_STR.getBytes());
public final static Integer MAX_FRAME_LEN=1000;
}
服务端实现
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.DelimiterBasedFrameDecoder;
/**
* Created by hsc on 17/10/14.
*/
public class NettyServer {
private static void startServer(final int port) throws Exception {
final EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.channel(NioServerSocketChannel.class)
.group(boss, worker)
.childOption(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//注意DelimiterBasedFrameDecoder和ServerHandler顺序
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(DelimiterConstant.MAX_FRAME_LEN, DelimiterConstant.DELIMITER_BUF));
ch.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//等待服务端监听端口关闭
channelFuture.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
startServer(8080);
}
}
SeverHandler 实现
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf byteBuf = (ByteBuf) msg;
byte[] data = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(data);
System.out.println(new String(data));
String responseMsg="收到"+ DelimiterConstant.DELIMITER_STR;
ByteBuf sendMsg= Unpooled.copiedBuffer(responseMsg.getBytes());
ctx.writeAndFlush(sendMsg);
}
}
}
客户端实现
import com.hsc.study.codec.DelimiterConstant;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
* Created by apple on 17/10/1.
*/
public class NettyClient {
public static void main(String[] args) {
startClient();
}
private static void startClient() {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.channel(NioSocketChannel.class)
.group(eventLoopGroup)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(DelimiterConstant.MAX_FRAME_LEN, DelimiterConstant.DELIMITER_BUF));
ch.pipeline().addLast(new ClientHandler());
}
});
//发起异步连接操作
ChannelFuture future= bootstrap.connect("127.0.0.1", 8080).sync();
//等待客户端链路关闭
future.channel().closeFuture().sync();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
ClientHandler 实现
import com.hsc.study.codec.DelimiterConstant;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Created by apple on 17/10/1.
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端开始读数据");
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
System.out.println(new String(data));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//向服务端发送100 个hello world
String message = "hello world" + DelimiterConstant.DELIMITER_STR;
for (int i = 0; i < 10; i++) {
ByteBuf buf = Unpooled.copiedBuffer(message.getBytes());
ctx.writeAndFlush(buf);
}
}
}
不难发现当我们 DelimiterBasedFrameDecoder maxFrameLength设置比较小时候比如为8,也就是说在八字节无法读取到分割符,netty就会抛如下异常。
io.netty.handler.codec.TooLongFrameException: frame length exceeds 8: 11 - discarded
at io.netty.handler.codec.DelimiterBasedFrameDecoder.fail(DelimiterBasedFrameDecoder.java:300)
at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:266)
at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:216)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
at java.lang.Thread.run(Thread.java:748)
2 定长解码
netty 中提供了FixedLengthFrameDecoder 作为定长解码器,使用方式和前面类似,这里就不在赘述。
ch.pipeline().addLast(new FixedLengthFrameDecoder(1024));
参考文献
[1] Netty 权威指南(第二版),李林峰著