黏包&拆包处理
网络传输的时候可能是到达一定的量比如4k会发送一次,可能会把多个消息一起推送过来 就是黏包。如果没有很好的协议解析这一段字节流,就会造成无法解析的情况。
解决方案可以是,按特定的格式去定义每一条消息,比如文件头+消息体,文件头里面描述消息体的大小,这样接受端就知道如何解析每一条消息了。
netty提供了很多frame的编解码器 本次解析LengthFieldBaseFrameDecoder
黏包出现 客户端发了10条数句,server端有时候 只打印一次,10条数据被融合了
public class ServerHandlerChai extends SimpleChannelInboundHandler<ByteBuf> {
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] bytes = new byte[msg.readableBytes()];
msg.readBytes(bytes);
String content = new String(bytes , Charset.forName("UTF-8"));
System.out.println("收到客户端的消息======"+content);
System.out.println("收到客户端的消息 读取的次数======"+ (++count));
ByteBuf byteBuf = Unpooled.copiedBuffer("这是服务器送给客户端的一段话" , Charset.forName("UTF-8"));
ctx.writeAndFlush(byteBuf);
}
}
public class SimpleClientHandlerChai extends SimpleChannelInboundHandler<ByteBuf> {
int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf msg) {
byte[] bytes = new byte[msg.readableBytes()];
msg.readBytes(bytes);
String content = new String(bytes , Charset.forName("UTF-8"));
System.out.println(String.format("消息内容-> %s", content));
System.out.println(String.format("消息次数-> %s", ++count));
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
for(int i=0;i<=10;i++){
ByteBuf byteBuf = Unpooled.copiedBuffer("客户端来信================= "+i+"/r/n" , Charset.forName("UTF-8"));
ctx.writeAndFlush(byteBuf);
}
}
}
如何破解拆包黏包,自定义协议解析器
重点看下编码器和解码器
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroupChai = new NioEventLoopGroup();
NioEventLoopGroup workGroupChai = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap().
group(bossGroupChai , workGroupChai).
channel(NioServerSocketChannel.class).
childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("decoder" , new DeppyuDecoder());
pipeline.addLast("encoder" , new DeppyuEncoder());
pipeline.addLast("serverHandler", new ServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(5566)).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroupChai.shutdownGracefully();
workGroupChai.shutdownGracefully();
}
}
}
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventGroup = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap().group(eventGroup).
channel(NioSocketChannel.class).
handler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("decoder" , new DeppyuDecoder());
pipeline.addLast("encoder" , new DeppyuEncoder());
pipeline.addLast("clientHandler", new ClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost",5566).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventGroup.shutdownGracefully();
}
}
}
public class ServerHandler extends SimpleChannelInboundHandler<DeppyuProtocol> {
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, DeppyuProtocol msg) throws Exception {
String content = new String(msg.getBytes());
System.out.println(String.format("收到服务器的消息内容-> %s", content));
String answer = "做个好人吧";
byte[] contentBytes = answer.getBytes();
int length = contentBytes.length;
DeppyuProtocol deppyu = new DeppyuProtocol();
deppyu.setLength(length);
deppyu.setBytes(contentBytes);
ctx.writeAndFlush(deppyu);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(String.format("[channelActive]-> [%s] 上线", channel.remoteAddress()));
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
// System.out.println(String.format("[channelInactive]-> [%s] 下线", channel.remoteAddress()));
channelGroup.writeAndFlush(String.format("[channelInactive]-> [%s] 下线 \n", channel.remoteAddress()));
super.channelInactive(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
if(channelGroup.size()>=1){
channelGroup.writeAndFlush(String.format("[handlerAdded]-> [%s] 加入 \n", channel.remoteAddress()));
}
channelGroup.add(channel);
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush(String.format("[服务端]-> [%s] 断开连接 \n", channel.remoteAddress()));
super.handlerRemoved(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
super.channelUnregistered(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
public class ClientHandler extends SimpleChannelInboundHandler<DeppyuProtocol> {
int count = 0;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DeppyuProtocol msg) {
String content = new String(msg.getBytes());
System.out.println(String.format("收到服务器的消息内容-> %s", content));
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
for(int i=0;i<=10;i++){
String content = "客户端来信==="+i;
int length = content.getBytes().length;
byte[] bytes = content.getBytes();
DeppyuProtocol deppyu = new DeppyuProtocol();
deppyu.setLength(length);
deppyu.setBytes(bytes);
ctx.writeAndFlush(deppyu);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常======"+cause.getMessage());
super.exceptionCaught(ctx, cause);
}
}
public class DeppyuEncoder extends MessageToByteEncoder<DeppyuProtocol> {
@Override
public void encode(ChannelHandlerContext ctx, DeppyuProtocol msg, ByteBuf out){
System.out.println("encode===========");
out.writeInt(msg.getLength());
out.writeBytes(msg.getBytes());
}
}
public class DeppyuDecoder extends ReplayingDecoder<Void> {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out){
System.out.println("decode===========");
int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content);
DeppyuProtocol deppyu = new DeppyuProtocol();
deppyu.setLength(length);
deppyu.setBytes(content);
out.add(deppyu);
}
}
关于replayingDecoer为啥可以认为数据全部到齐,具体的可以看链接的文章
大体和它的javadoc描述类似,设置mark点,如果数据没到齐 抛异常,后续再处理
ReplayingDecoder传递一个专门的ByteBuf实现,当缓冲区中没有足够的数据时,这个实现会抛出某种类型的错误。在上面的IntegerHeaderFrameDecoder中,您只是假设在调用buf.readInt()时,缓冲区中有4个或更多字节。如果缓冲区中确实有4个字节,它将像您期望的那样返回整数报头。否则,将引发错误并将控制返回到ReplayingDecoder。如果ReplayingDecoder捕捉到错误,那么它会将缓冲区的readerIndex倒回“初始”位置(即缓冲区的开始),并在缓冲区接收到更多数据时再次调用decode(..)方法。
请注意,ReplayingDecoder总是抛出相同的缓存错误实例,以避免每次抛出时创建新错误并填充其堆栈跟踪的开销。