• netty优化 (-) websocket解码异常后自动断开连接处理


    背景:

      公司需要25台设备组网,用户通过客户端登录后对25台机子进行监控操作(包括视频播放)。 

    技术方案:

      产品分为设备端、客户端、服务端。为兼容以后的浏览器访问,选java搭建服务器。服务器主要业务包括客户端用户管理、客户端业务指令、权限;设备端登记、发现、在线监测、分组管理、权限。

      由于环境比较简单,后台服务采用netty的websocket协议进行通信,消息指令进行权限管理。

    问题描述:

      1、25台设备搭建后进行压力测试,百兆路由可25路视频的2个客户端,3个客户端同时打开会导致设备掉线频繁,(添加重连限制客户端个数)。

      2、OOM,outof direct memory,此问题很懵逼。netty中derectmemory 是框架中进行计数处理的,测试中计数增长到一定值后保持稳定不存在超出;channelread0方法中会自动释放bytebuf; 此问题无法重现,只好添加jvm内存待以后重现再处理!

      3、长时间挂机无任何操作出现客户端或者设备掉线问题,查看日志多是和decode解码有关,消息异常解码出错,netty自动关闭通道断开了连接。

          测试结果:设备端掉线明显;消息解析错误后直接关闭了连接;偶尔出现一个大的数据包接收一半后断开连接;

        websocket基于TCP协议,在不稳定的网络环境下发送大量数据,并且发送频率非常高,很可能会出现错误(1、程序处理逻辑错误;2、多线程同步问题;3、缓冲区溢出等)。这掉线的频率让人很难接收,抓包也是抓的崩溃, 放弃了! 几个同事之间可能也都踢了好几周的皮球,呵呵,感觉对不起公司的同事们。首先让客户端和设备端全部添加了断线重连、优化设备端发送频率、服务端缓存一些消息。

       业务上做了优化之后,掉线有所缓解,但是偶尔一次的掉线的确让人抓狂,尤其是这么小的局域网中,为了从这个锅中脱离, 决定还是要有所优化, 可怕的框架bug ~~

       A.下netty参数,消息队列默认128 ,加到1024 ; 避免数据包的缓存

        ServerBootstrap b = new ServerBootstrap();
        b.option(ChannelOption.SO_BACKLOG, 1024)
        .childOption(ChannelOption.TCP_NODELAY,true)

         B.  异常不关闭 

          重写WebSocketDecoderConfig.closeOnProtocolViolation修改默认值。

          ByteToMessageDecoder 中解析完后,

          callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) 方法中 将 WebSocket08FrameDecoder的 state 重置为 WebSocket08FrameDecoder.State.READING_FIRST

          重写ByteToMessageDecoder .java 中callDecode 添加抽象方法initState

        protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
            try {
                while(true) {
                    if (in.isReadable()) {
                        int outSize = out.size();
                        if (outSize > 0) {
                            fireChannelRead(ctx, out, outSize);
                            out.clear();
                            if (ctx.isRemoved()) {
                                return;
                            }
    
                            outSize = 0;
                        }
    
                        int oldInputLength = in.readableBytes();
                        this.decodeRemovalReentryProtection(ctx, in, out);
                        if (!ctx.isRemoved()) {
                            if (outSize == out.size()) {
                                if (oldInputLength != in.readableBytes()) {
                                    continue;
                                }
                            } else {
                                if (oldInputLength == in.readableBytes()) {
                                    throw new DecoderException(StringUtil.simpleClassName(this.getClass()) + ".decode() did not read anything but decoded a message.");
                                }
    
                                if (!this.isSingleDecode()) {
                                    continue;
                                }
                            }
                        }
                    }
                    if (  ctx.name().equals("wsdecoder")){
                        try{
                            this.initState(ctx, in, out);
                        }catch (Exception E){
                        }
                    }
                    return;
                }
    
    
    
            } catch (DecoderException var6) {
                throw var6;
            } catch (Exception var7) {
                throw new DecoderException(var7);
            }
        }
        protected abstract void initState(ChannelHandlerContext var1, ByteBuf var2, List<Object> var3) throws Exception;
    

      

    重写WebSocket08FrameDecoder.java 中添加initState
        protected void initState(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            if(this.state == WebSocket08FrameDecoder.State.CORRUPT){
                this.state = WebSocket08FrameDecoder.State.READING_FIRST;
            }
    
        }

          不将state 设置为READING_FIRST ,通道解析出现异常后,WebSocket08FrameDecoder每次消息解析都会走CORRUPT,跳过了正常解析 。

  • 相关阅读:
    Javascript之DOM的三大节点及部分用法
    Javascript之全局变量和局部变量部分讲解
    《TCP/IP详解 卷1:协议》系列分享专栏
    说一说MySQL的锁机制
    《TCP/IP详解 卷1:协议》第3章 IP:网际协议
    PHP连接MySql闪断自动重连的方法
    关于MySQL的锁机制详解
    React 源码中的依赖注入方法
    《Mysql高级知识》系列分享专栏
    《AngularJS学习整理》系列分享专栏
  • 原文地址:https://www.cnblogs.com/heshana/p/13597461.html
Copyright © 2020-2023  润新知