基于 Netty 实现的 WebSocket 服务端
目录一、短轮询Short Polling二、长轮询Long Polling三、WebSocket 长连接四、总结五、基于 Netty 实现的 WebSocket 服务端1、整体架构说明2、NettyWebSocketServerWebSocket 服务端1. 线程组创建2. 启动方法3. 销毁方法4. 服务器启动逻辑5. 初始化管道ChannelPipeline3、NettyWebSocketServerHandler消息与事件处理1. 连接事件处理2. 消息读取与业务处理3. 根据类型分发逻辑4、整体运行流程图5、总结六、附录代码在许多业务场景中服务端都需要主动向 Web 客户端推送消息不仅仅限于 IM 通讯系统。例如小红点提醒新消息提示审批流通知为了实现这些效果通常会有几种服务端推送 Web 的方案。下面分别介绍三种常见方式一、短轮询Short Polling原理短轮询是指Web 端不断地以固定时间间隔向服务端发送 HTTP 请求。如果服务端有新消息就会在某次请求中返回。示例阿斌之前做过的一个 OA 系统中为了让用户实时收到审批流提醒或小红点提示客户端每秒向服务端发送一次请求等待后端返回数据。适用场景扫码登录短时间内频繁查询二维码状态。小型 OA 系统客户端数量不大服务端压力较小。缺点大量无效请求大部分请求没有新消息返回浪费服务器资源。服务端压力大在高并发场景如万人群聊下频繁请求会导致服务端难以承受。实现思路客户端浏览器以固定时间间隔例如 1 秒或 5 秒不断向服务端发起 HTTP 请求检查是否有新消息。前端示例JavaScript// 每隔 5 秒向后端请求一次setInterval((){fetch(/api/notice).then(resres.json()).then(data{if(data.hasMessage){console.log( 有新消息:,data.message);}else{console.log(无新消息);}}).catch(errconsole.error(请求错误:,err));},5000);二、长轮询Long Polling原理长轮询是对短轮询的一种改进。不同点在于当请求没有新消息时服务端不会立即返回而是将请求“挂起Hang”一段时间。如果在这段时间内有新消息产生服务端立即返回如果超时仍无消息再返回空响应Web 端再发起下一次请求。因此客户端的请求超时时间需要设置得更长一些。优点相比短轮询大幅减少了无效请求降低了网络和服务器 QPS每秒请求数客户端功耗更低。缺点仍有无效请求若等待时间内无消息仍需重新发起请求服务端压力依然较大虽然降低了入口请求频率但悬挂的请求仍会占用线程或连接资源。例如若有 1000 个请求在等待就可能有 1000 个线程在轮询后端存储资源。实现思路客户端发起请求后服务端不立即响应。如果有新数据立即返回否则**挂起一段时间例如 30 秒**后再返回空结果。客户端收到响应后再发起下一次请求。前端示例JavaScriptfunctionlongPolling(){fetch(/api/long-poll).then(resres.json()).then(data{if(data.hasMessage){console.log( 新消息:,data.message);}else{console.log(⏳ 暂无消息);}// 继续下一轮请求longPolling();}).catch(err{console.error(连接错误:,err);// 等待 3 秒后重试setTimeout(longPolling,3000);});}// 启动长轮询longPolling();三、WebSocket 长连接原理相比轮询方式WebSocket 是一种真正的双向通信方案。通过在客户端与服务端之间建立一个持久的 TCP/IP 长连接实现全双工Full-Duplex通信即服务端可以主动向客户端推送数据。优点实现真正的实时通信省去了轮询带来的网络与性能损耗更高效、更节能。缺点实现复杂度较高需要维护连接状态、心跳检测、断线重连等逻辑。四、总结推送方案优点缺点适用场景短轮询实现简单适合轻量场景请求频繁、性能浪费大扫码登录、小型系统长轮询降低无效请求服务端压力仍大中等规模系统WebSocket实时推送、性能高效实现复杂、需维护连接大型系统、IM、通知推送五、基于 Netty 实现的 WebSocket 服务端1、整体架构说明项目主要分成两个类类名作用NettyWebSocketServer启动一个 WebSocket 服务器负责网络层NettyWebSocketServerHandler处理客户端消息和连接事件负责业务层整个流程大致是启动服务器 → 客户端连接 → HTTP 升级为 WebSocket → 建立长连接 → 处理消息与心跳。2、NettyWebSocketServerWebSocket 服务端1. 线程组创建privateEventLoopGroupbossGroupnewNioEventLoopGroup(1);privateEventLoopGroupworkerGroupnewNioEventLoopGroup(NettyRuntime.availableProcessors());bossGroup只负责接收客户端连接请求。workerGroup负责处理连接的读写事件。NioEventLoopGroup基于 NIO 实现的事件循环线程池。简单理解boss 是“门卫”worker 是“工人”。2. 启动方法PostConstructpublicvoidstart()throwsInterruptedException{run();}PostConstruct表示当 Spring 容器启动时自动执行。调用run()启动 WebSocket 服务器。3. 销毁方法PreDestroypublicvoiddestroy(){bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}在容器关闭时释放资源优雅关闭线程组。4. 服务器启动逻辑ServerBootstrapserverBootstrapnewServerBootstrap();ServerBootstrap是 Netty 启动服务器的引导类。然后配置serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,128).option(ChannelOption.SO_KEEPALIVE,true).handler(newLoggingHandler(LogLevel.INFO))含义channel(...)使用 NIO 的 TCP 通信通道SO_BACKLOG允许的最大排队连接数SO_KEEPALIVE启用 TCP 心跳保活机制LoggingHandler打印连接日志方便调试。5. 初始化管道ChannelPipeline.childHandler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinepipelinech.pipeline();...}});这里是 WebSocket 的“核心逻辑链”每一个连接都会被分配一个“管道pipeline”。管道中添加的处理器如下处理器功能IdleStateHandler(30,0,0)30秒内无读操作则触发“读空闲”事件可用来检测心跳超时HttpServerCodec()HTTP 编解码器WebSocket 握手阶段需要 HTTPChunkedWriteHandler()支持大数据流分块写入比如文件传输HttpObjectAggregator(8192)将 HTTP 的分段消息聚合成完整请求最大8KBWebSocketServerProtocolHandler(/)负责将 HTTP 升级为 WebSocket 协议并保持长连接NettyWebSocketServerHandler()自定义的业务逻辑处理类见下最后一句serverBootstrap.bind(WEB_SOCKET_PORT).sync();启动服务器监听8090端口。3、NettyWebSocketServerHandler消息与事件处理继承publicclassNettyWebSocketServerHandlerextendsSimpleChannelInboundHandlerTextWebSocketFrameTextWebSocketFrame表示 WebSocket 的文本帧即发送的文本消息。1. 连接事件处理OverridepublicvoiduserEventTriggered(ChannelHandlerContextctx,Objectevt)握手事件if(evtinstanceofWebSocketServerProtocolHandler.HandshakeComplete){System.out.println(握手完成);}当 HTTP 协议成功升级为 WebSocket 时打印“握手完成”。心跳超时elseif(evtinstanceofIdleStateEvent){if(event.state()IdleState.READER_IDLE){System.out.println(读空闲);ctx.channel().close();}}超过 30 秒没收到消息空闲说明客户端断开或网络异常服务器主动关闭连接。2. 消息读取与业务处理OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,TextWebSocketFramemsg)当客户端发来消息时执行。Stringtextmsg.text();WSBaseReqwsBaseReqJSONUtil.toBean(text,WSBaseReq.class);将客户端发送的 JSON 文本反序列化为WSBaseReq对象WSBaseReq里通常有一个字段表示消息类型type。3. 根据类型分发逻辑switch(WSReqTypeEnum.of(wsBaseReq.getType())){caseAUTHORIZE:break;caseHEARTBEAT:break;caseLOGIN:System.out.println(请求二维码);ctx.channel().writeAndFlush(newTextWebSocketFrame(123));}类型含义行为AUTHORIZE授权登录验证暂未实现HEARTBEAT心跳检测暂未实现LOGIN登录请求如扫码登录打印“请求二维码”并向客户端返回字符串“123”4、整体运行流程图浏览器(客户端) ↓ HTTP握手 NettyWebSocketServer ↓ 升级协议(状态码101) ↓ 建立长连接 NettyWebSocketServerHandler ↓ 监听消息(TextWebSocketFrame) ↓ 分发业务逻辑 ↓ 服务器可随时向客户端推送消息5、总结模块职责NettyWebSocketServer搭建底层服务器、配置管道、维持长连接NettyWebSocketServerHandler处理业务逻辑握手、心跳、消息分发IdleStateHandler心跳检测断线清理WebSocketServerProtocolHandlerHTTP → WebSocket 协议升级TextWebSocketFrameWebSocket 的消息载体六、附录代码package com.donglin.mallchat.common.websocket;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.ChannelPipeline;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.http.HttpObjectAggregator;import io.netty.handler.codec.http.HttpServerCodec;import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;import io.netty.handler.logging.LogLevel;import io.netty.handler.logging.LoggingHandler;import io.netty.handler.stream.ChunkedWriteHandler;import io.netty.handler.timeout.IdleStateHandler;import io.netty.util.NettyRuntime;import io.netty.util.concurrent.Future;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;Slf4j ConfigurationpublicclassNettyWebSocketServer{publicstaticfinalintWEB_SOCKET_PORT8090;// 创建线程池执行器privateEventLoopGroupbossGroupnewNioEventLoopGroup(1);privateEventLoopGroupworkerGroupnewNioEventLoopGroup(NettyRuntime.availableProcessors());/** * 启动 ws server * * return * throws InterruptedException */PostConstructpublicvoidstart()throwsInterruptedException{run();}/** * 销毁 */PreDestroypublicvoiddestroy(){Future?futurebossGroup.shutdownGracefully();Future?future1workerGroup.shutdownGracefully();future.syncUninterruptibly();future1.syncUninterruptibly();log.info(关闭 ws server 成功);}publicvoidrun()throwsInterruptedException{// 服务器启动引导对象ServerBootstrapserverBootstrapnewServerBootstrap();serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,128).option(ChannelOption.SO_KEEPALIVE,true).handler(newLoggingHandler(LogLevel.INFO))// 为 bossGroup 添加 日志处理器.childHandler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelsocketChannel)throwsException{ChannelPipelinepipelinesocketChannel.pipeline();//30秒客户端没有向服务器发送心跳则关闭连接pipeline.addLast(newIdleStateHandler(30,0,0));// 因为使用http协议所以需要使用http的编码器解码器pipeline.addLast(newHttpServerCodec());// 以块方式写添加 chunkedWriter 处理器pipeline.addLast(newChunkedWriteHandler());/** * 说明 * 1. http数据在传输过程中是分段的HttpObjectAggregator可以把多个段聚合起来 * 2. 这就是为什么当浏览器发送大量数据时就会发出多次 http请求的原因 */pipeline.addLast(newHttpObjectAggregator(8192));//保存用户ip// pipeline.addLast(new HttpHeadersHandler());/** * 说明 * 1. 对于 WebSocket它的数据是以帧frame 的形式传递的 * 2. 可以看到 WebSocketFrame 下面有6个子类 * 3. 浏览器发送请求时 ws://localhost:7000/hello 表示请求的uri * 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议保持长连接 * 是通过一个状态码 101 来切换的 */pipeline.addLast(newWebSocketServerProtocolHandler(/));// 自定义handler 处理业务逻辑pipeline.addLast(newNettyWebSocketServerHandler());}});// 启动服务器监听端口阻塞直到启动成功serverBootstrap.bind(WEB_SOCKET_PORT).sync();}}package com.donglin.mallchat.common.websocket;import cn.hutool.json.JSONUtil;import com.abin.mallchat.common.websocket.domain.enums.WSReqTypeEnum;import com.abin.mallchat.common.websocket.domain.vo.req.WSBaseReq;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;import io.netty.handler.timeout.IdleState;import io.netty.handler.timeout.IdleStateEvent;/** * Description: * Author: a hrefhttps://github.com/zongzibinbinabin/a * Date: 2023-08-27 */publicclassNettyWebSocketServerHandlerextendsSimpleChannelInboundHandlerTextWebSocketFrame{OverridepublicvoiduserEventTriggered(ChannelHandlerContextctx,Objectevt)throwsException{if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){System.out.println(握手完成);}elseif(evtinstanceofIdleStateEvent){IdleStateEventevent(IdleStateEvent)evt;if(event.state()IdleState.READER_IDLE){System.out.println(读空闲);//todo 用户下线ctx.channel().close();}}}OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,TextWebSocketFramemsg)throwsException{Stringtextmsg.text();WSBaseReqwsBaseReqJSONUtil.toBean(text,WSBaseReq.class);switch(WSReqTypeEnum.of(wsBaseReq.getType())){caseAUTHORIZE:break;caseHEARTBEAT:break;caseLOGIN:System.out.println(请求二维码);ctx.channel().writeAndFlush(newTextWebSocketFrame(123));}}}

相关新闻

老电表RS485接入云平台的三种落地路径

老电表RS485接入云平台的三种落地路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 1:45:11 阅读更多 →
Agent双存储记忆架构:Working Memory与Persistent Memory实战设计

Agent双存储记忆架构:Working Memory与Persistent Memory实战设计

1. 什么是Agent Memory?它到底在解决什么问题?“Agent Memory架构设计与实现”这个标题乍看像一个技术名词堆砌,但背后藏着当前AI工程落地最棘手的现实瓶颈——不是模型不够大,而是Agent记不住、想不全、用不对。我从2022年第一批…

2026/10/9 1:45:11 阅读更多 →
Apache Storm Stream API 实战指南:用类型化 DSL 构建流式计算拓扑

Apache Storm Stream API 实战指南:用类型化 DSL 构建流式计算拓扑

后端大数据 【免费下载链接】storm Apache Storm 项目地址: https://gitcode.com/gh_mirrors/storm22/storm 点击查看 免费下载 本指南围绕 Apache Storm 的 Stream API(org.apache.storm.streams 包)展开,介绍如何以类型化、函数…

2026/10/9 1:44:10 阅读更多 →

最新新闻

双指针算法全攻略:对撞、快慢、滑动窗口三大模板与实战总结

双指针算法全攻略:对撞、快慢、滑动窗口三大模板与实战总结

刷题刷到一定量,很多人会慢慢总结出一条规律:有一类题的解法特别“固定”——有序数组里找两个数凑目标值、链表中判断有没有环、字符串里找不重复的最长子串,题面长得完全不一样,翻开题解一看,底层全是同一个思路&…

2026/10/9 3:32:13 阅读更多 →
医疗NLP实战:词典构建与最大匹配实体标注

医疗NLP实战:词典构建与最大匹配实体标注

简介:一套基于Python与Jupyter构建的医疗实体识别模型资源,面向疾病、症状、身体部位三类实体,完整呈现词典构造、语料标注、模型训练与结果评估的工程化流程。压缩包共147个文件,约581MB,具体包含18个txt词典/文本、1…

2026/10/9 3:32:13 阅读更多 →
Git远程分支覆盖本地分支:reset、clean实操与急救指南

Git远程分支覆盖本地分支:reset、clean实操与急救指南

1. 什么时候需要“用远程分支覆盖本地分支”先聊个真实的场景。我在维护一个项目时,远程仓库里develop分支已经被同事 rebase 重新整理过,提交历史完全换了样子。我本地还停在老版本上,这时候直接git pull会提示分叉严重,甚至直接…

2026/10/9 3:32:13 阅读更多 →
Cache模拟器实战:从映射原理到命中率计算的完整工程解析

Cache模拟器实战:从映射原理到命中率计算的完整工程解析

简介:一份面向计算机组成原理与操作系统学习者的缓存模拟器源码,在Visual Studio 2010环境下编写,通过读取地址流文件模拟处理器访存行为,可设置缓存容量、块大小,并支持直接映射、组关联映射、全关联映射三种策略&…

2026/10/9 3:32:13 阅读更多 →
Servlet配置实战:web.xml与@WebServlet注解全面解析

Servlet配置实战:web.xml与@WebServlet注解全面解析

Servlet这个词,放在今天动辄微服务、云原生的大环境下,多少有点“老古董”的感觉。但你只要还在写Java后端,不管用Spring Boot还是Spring MVC,请求真正进来之后,最终处理的还是Servlet容器那一层。很多新人会直接跳过S…

2026/10/9 3:32:13 阅读更多 →
Claude Code与桌面版安装教程:环境配置、VS Code插件及MCP部署

Claude Code与桌面版安装教程:环境配置、VS Code插件及MCP部署

最近一直被同一个问题刷屏:“Claude到底怎么装?”尤其是Claude Code这三个月火起来之后,各大群里问安装的比问用法的还多。我前前后后帮朋友远程装过几十次,也踩了不少坑——什么安装到一半卡死、装完打开白屏、输入命令提示找不到…

2026/10/9 3:31:13 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →