• Netty(四):粘包问题描述及解决


    拆包粘包问题解决

    netty使用tcp/ip协议传输数据。而tcp/ip协议是类似水流一样的数据传输方式。多次访问的时候有可能出现数据粘包的问题,解决这种问题的方式如下:

    定长数据流

    客户端和服务器,提前协调好,每个消息长度固定。(如:长度10)。如果客户端或服务器写出的数据不足10,则使用空白字符补足(如:使用空格)。

    代码示例

    a.客户端

    public class MyClient {
        
        // 处理请求和处理服务端响应的线程组
        private EventLoopGroup group = null;
        // 服务启动相关配置信息
        private Bootstrap bootstrap = null;
        
        public MyClient(){
            init();
        }
        
        private void init(){
            group = new NioEventLoopGroup();
            bootstrap = new Bootstrap();
            // 绑定线程组
            bootstrap.group(group);
            // 设定通讯模式为NIO
            bootstrap.channel(NioSocketChannel.class);
        }
        
        public ChannelFuture doRequest(String host, int port) throws InterruptedException{
            this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelHandler[] handlers = new ChannelHandler[3];
                    handlers[0] = new FixedLengthFrameDecoder(3);
                    // 字符串解码器Handler,会自动处理channelRead方法的msg参数,将ByteBuf类型的数据转换为字符串对象
                    handlers[1] = new StringDecoder(Charset.forName("utf8"));
                    handlers[2] = new MyClientHandler();
                    
                    ch.pipeline().addLast(handlers);
                }
            });
            ChannelFuture future = this.bootstrap.connect(host, port).sync();
            return future;
        }
        
        public void release(){
            this.group.shutdownGracefully();
        }
        
        public static void main(String[] args) {
            MyClient client = null;
            ChannelFuture future = null;
            try{
                client = new MyClient();
                
                future = client.doRequest("localhost", 8000);
                
                Scanner s = null;
                while(true){
                    s = new Scanner(System.in);
                    System.out.print("客户端输入:");
                    future.channel().writeAndFlush(Unpooled.copiedBuffer(s.nextLine().getBytes("utf8")));
                    TimeUnit.SECONDS.sleep(1);
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                if(null != future){
                    try {
                        future.channel().closeFuture().sync();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(null != client){
                    client.release();
                }
            }
        }
        
    }
    public class MyClientHandler extends ChannelHandlerAdapter {
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            try{
                System.out.println("from server : " + msg.toString());
            }finally{
                // 用于释放缓存。避免内存溢出
                ReferenceCountUtil.release(msg);
            }
        }
    
        /**
         * 异常处理
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println(cause.getMessage());
            ctx.close();
        }
    
    }

    b.服务端

    public class MyServer {
        private EventLoopGroup acceptorGroup = null;
        private EventLoopGroup clientGroup = null;
        private ServerBootstrap bootstrap = null;
        public MyServer(){
            init();
        }
        private void init(){
            acceptorGroup = new NioEventLoopGroup();
            clientGroup = new NioEventLoopGroup();
            bootstrap = new ServerBootstrap();
            // 绑定线程组
            bootstrap.group(acceptorGroup, clientGroup);
            // 设定通讯模式为NIO
            bootstrap.channel(NioServerSocketChannel.class);
            // 设定缓冲区大小
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            // SO_SNDBUF发送缓冲区,SO_RCVBUF接收缓冲区,SO_KEEPALIVE开启心跳监测(保证连接有效)
            bootstrap.option(ChannelOption.SO_SNDBUF, 8*1024)
                .option(ChannelOption.SO_RCVBUF, 8*1024)
                .option(ChannelOption.SO_KEEPALIVE, true);
        }
        public ChannelFuture doAccept(int port) throws InterruptedException{
            
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelHandler[] acceptorHandlers = new ChannelHandler[3];
                    // 定长Handler。通过构造参数设置消息长度(单位是字节)。发送的消息长度不足可以使用空格补全。
                    acceptorHandlers[0] = new FixedLengthFrameDecoder(3);
                    acceptorHandlers[1] = new StringDecoder(Charset.forName("utf8"));
                    acceptorHandlers[2] = new MyServerHandler();
                    ch.pipeline().addLast(acceptorHandlers);
                }
            });
            ChannelFuture future = bootstrap.bind(port).sync();
            return future;
        }
        public void release(){
            this.acceptorGroup.shutdownGracefully();
            this.clientGroup.shutdownGracefully();
        }
        
        public static void main(String[] args){
            ChannelFuture future = null;
            MyServer server = null;
            try{
                server = new MyServer();
                future = server.doAccept(8000);
                System.out.println("服务已启动");
                future.channel().closeFuture().sync();
            }catch(InterruptedException e){
                e.printStackTrace();
            }finally{
                if(null != future){
                    try {
                        future.channel().closeFuture().sync();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                
                if(null != server){
                    server.release();
                }
            }
        }
        
    }
    public class MyServerHandler extends ChannelHandlerAdapter {
        
        // 业务处理逻辑
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("客户端消息 : " + msg.toString());
            ctx.writeAndFlush(Unpooled.copiedBuffer("ok ".getBytes("utf8")));
        }
        // 异常处理逻辑
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println(cause.getMessage());
            ctx.close();
        }
    }

    特殊结束符

    客户端和服务器,协商定义一个特殊的分隔符号,分隔符号长度自定义。如:#’、‘$_$’、‘AA@’。在通讯的时候,只要没有发送分隔符号,则代表一条数据没有结束。

    添加DelimiterBasedFrameDecoder特殊字符解码器并约定分隔符即可。

    客户端:

    this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // 定义数据分隔符
                    ByteBuf delimiter = Unpooled.copiedBuffer("$E$".getBytes());
                    ChannelHandler[] handlers = new ChannelHandler[3];
                    handlers[0] = new DelimiterBasedFrameDecoder(1024, delimiter);
                    handlers[1] = new StringDecoder(Charset.forName("UTF-8"));
                    handlers[2] = new MyClientHandler();
                    ch.pipeline().addLast(handlers);
                }
            });
            ChannelFuture future = this.bootstrap.connect(host, port).sync();
            return future;

    服务端:

    bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ByteBuf delimiter = Unpooled.copiedBuffer("$E$".getBytes());
                    ChannelHandler[] acceptorHandlers = new ChannelHandler[3];
                    acceptorHandlers[0] = new DelimiterBasedFrameDecoder(1024, delimiter);
                    acceptorHandlers[1] = new StringDecoder(Charset.forName("UTF-8"));
                    acceptorHandlers[2] = new MyServerHandler();
                    ch.pipeline().addLast(acceptorHandlers);
                }
            });
            ChannelFuture future = bootstrap.bind(port).sync();
            return future;

    更多看这个:Netty中解码基于分隔符的协议和基于长度的协议

    协议

    相对最成熟的数据传递方式。有服务器的开发者提供一个固定格式的协议标准。客户端和服务器发送数据和接受数据的时候,都依据协议制定和解析消息。

    http协议实现看这里

    Netty 实现简单的HTTP服务

    构建基于Netty 的HTTP/HTTPS 应用程序

     

  • 相关阅读:
    【转】微信小程序开发之图片等比例缩放 获取屏幕尺寸图片尺寸 自适应
    解决vscode egg调试出现: this socket has been ended by other party【转】
    高仿Readhub小程序 微信小程序项目【原】
    git 解决二进制文件冲突
    webpack 打包编译-webkit-box-orient: vertical 后消失
    H5 history.pushState 在微信内修改url后点击用safari打开/复制链接是修改之前的页面
    vue 路由懒加载 使用,优化对比
    jq自定义多选下拉列表框
    System V IPC相关函数
    互斥锁和条件变量(pthread)相关函数
  • 原文地址:https://www.cnblogs.com/shamo89/p/9637764.html
Copyright © 2020-2023  润新知