Netty通信中的粘包半包问题(二)

在前面我们已经分析过Netty会出现的粘包半包问题,还没看过前面的博客的,可以先去看下之前写的博客
Netty通信中的粘包半包问题(一)
解放方式:特殊分隔符解决,在每个报文后面加上一个特殊分隔符,以此来告诉服务端每个报文的数据结界是什么

1.Server

package splicing.delimiter;

import constant.Constant;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;

import java.net.InetSocketAddress;

public class DelimiterEchoServer {
    
    public static final String DELIMITER_SYMBOL = "@~";

    public static void main(String[] args) throws InterruptedException {
        DelimiterEchoServer delimiterEchoServer = new DelimiterEchoServer();
        System.out.println("服务器即将启动");
        delimiterEchoServer.start();
    }
    
    public void start() throws InterruptedException {
        DelimiterServerHandler serverHandler = new DelimiterServerHandler();
        // 线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 服务端启动必须
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)// 线程组传入
                    .channel(NioServerSocketChannel.class) // 指定使用NIO进行网络传输
                    .localAddress(new InetSocketAddress(Constant.DEFAULT_PORT)) //指定服务器监听端口
                    // 服务端没接收到一个连接请求,就会重启一个socket通信,也就是channel
                    // 所以下面这段代码的作用就是为这个子channel增加handler
                    .childHandler(new ChannelInitializerImp());

            ChannelFuture f = b.bind().sync();
            System.out.println("服务器启动完成,等待客户端的连接和数据....");
            // 阻塞直到服务器的channel关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅关闭线程组
            group.shutdownGracefully().sync();
        }
    }
    
    private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel channel) throws Exception {
            ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_SYMBOL.getBytes());
            channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
            channel.pipeline().addLast(new DelimiterServerHandler());
        }
    }
}

package splicing.delimiter;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class DelimiterServerHandler extends ChannelInboundHandlerAdapter {
    
    private AtomicInteger counter = new AtomicInteger(0);

    /**
     * 服务端读取到网络数据后的处理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server Accept[" + request 
                + "] and counter is :" + counter.incrementAndGet());
        
        String resp = "Hello," + request 
                + ". Welcome to Netty World!" 
                + DelimiterEchoServer.DELIMITER_SYMBOL;
        ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
//        super.channelRead(ctx, msg);
    }

    /**
     * 服务端读取完成网络数据后的处理
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//        super.channelReadComplete(ctx);

        System.out.println("channelReadComplete-------");
//        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
//        super.exceptionCaught(ctx, cause);
    }
}

2.Client

package splicing.delimiter;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.util.concurrent.atomic.AtomicInteger;

public class DelimiterClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    private AtomicInteger counter = new AtomicInteger(0);

    /**
     * 客户端读取到网络数据后的处理
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf msg) throws Exception {
        System.out.println("client Accept[" + msg.toString(CharsetUtil.UTF_8) 
                + "] and the counter is :" + counter.incrementAndGet());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf msg = null;
        String request = "Mark,Zhuge,Zhouyu,fox,loulan" + DelimiterEchoServer.DELIMITER_SYMBOL;
//        super.channelActive(ctx);
        for (int i = 0; i < 100; i++) {
            
            msg = Unpooled.buffer(request.length());
            msg.writeBytes(request.getBytes());
            ctx.writeAndFlush(msg);
            System.out.println("发送数据到服务器");
        }
    }
}

package splicing.delimiter;

import constant.Constant;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import netscape.security.UserTarget;

import java.net.InetSocketAddress;

public class DelimiterEchoClient {
    
    private final String host;
    
    public DelimiterEchoClient(String host) {
        this.host = host;
    }
    
    public void start() throws InterruptedException {
        // 线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)// 将线程组传入
                    .channel(NioSocketChannel.class) // 使用指定NIO进行网络传输
                    .remoteAddress(new InetSocketAddress(host, Constant.DEFAULT_PORT)) //配置要连接服务器的ip地址和端口 
                    .handler(new ChannelInitializerImp());
            
            ChannelFuture f = b.connect().sync();
            System.out.println("已连接到服务器....");
            f.channel().closeFuture().sync();

        } finally {
            group.shutdownGracefully().sync();
        }
        
    }
    
    private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel channel) throws Exception {
            ByteBuf delimiter = 
                    Unpooled.copiedBuffer(DelimiterEchoServer.DELIMITER_SYMBOL.getBytes());
            channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
            channel.pipeline().addLast(new DelimiterClientHandler());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new DelimiterEchoClient(Constant.DEFAULT_SERVER_IP).start();
    }
}

3.结果展示

Netty通信中的粘包半包问题(二)_第1张图片
Netty通信中的粘包半包问题(二)_第2张图片

4.场景分析

在大多数场景下我们不能要求每个客户端都在报文后面添加一个特殊分隔符,
当然也可以在前端做特殊处理,但是总会感觉怪怪的,因为一旦报文中也出现这样的特殊分隔符,
将会影响业务对报文的分割处理,所以这个方案可能会存在设计上的漏洞,
下一期我们来分析下其他的解决方式

你可能感兴趣的:(网络IO,开发语言,java)