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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
1
2
3
4
5
6
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,跳过了正常解析 。

 

posted @   higsan  阅读(4296)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示