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,跳过了正常解析 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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)