ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Netty UDP工程化实践:高并发物联网通信骨架设计

Netty UDP工程化实践:高并发物联网通信骨架设计 1. 项目概述为什么在2024年还要深挖 Netty UDP 这个组合Netty UDP——这四个字乍看像一句技术黑话但背后藏着大量真实业务场景里被反复踩坑、又反复重写的通信底层逻辑。我从2015年开始做物联网网关中间件经手过几十个用 UDP 做设备直连的项目其中超过七成在初期都绕不开 Netty。不是因为“高大上”而是因为——原生 JDK 的 DatagramChannel 太原始写一个能扛住每秒万级报文、不丢包、不乱序、可热插拔编解码器、还能和 Spring Boot 容器生命周期对齐的 UDP 服务光靠new DatagramSocket()写三天也写不完更别说压测时发现的缓冲区溢出、线程阻塞、GC 频繁这些隐形炸弹。你搜到的那些热搜词比如“netty粘包处理”“udp网络调试”“wireshark如何筛选出udp前后两包的时间间隔”其实全是同一个问题的不同切面UDP 本身无连接、无确认、无重传、无序号它只负责把包发出去至于对方收没收到、是不是重复了、是不是拼错了——全得你自己兜底。而 Netty 的价值恰恰在于它把这套“兜底工程”标准化、模块化、可观测化了。它不是替代 UDP而是让 UDP 可以被真正工程化地使用。举个最典型的例子某智能充电桩项目终端用 ESP32 模组通过 UDP 上报心跳64 字节纯二进制要求 30 秒超时断连、支持 5 万台设备并发、单机 CPU 占用低于 15%。如果用传统DatagramSocketExecutorService手撸你会卡在三个地方一是多线程读取时receive()调用阻塞导致线程池耗尽二是没有统一的 ByteBuf 内存池小包高频收发引发频繁 GC三是无法像 TCP 那样天然按连接隔离上下文设备 ID 只能靠解析报文头提取一旦解析失败就整条链路崩掉。而 Netty 的NioDatagramChannelEventLoopGroupChannelPipeline三件套直接把这三个痛点拆解成可配置、可替换、可监控的组件。所以这篇内容不是讲“Netty 怎么启动 UDP 服务”的 Hello World而是聚焦于一个有生产经验的工程师在真实项目中面对 UDP 场景时如何用 Netty 构建一套稳定、可观测、易维护、能上线的通信骨架。它适合三类人正在做 IoT/音视频/游戏实时通信的后端开发准备 Netty 面试题、但只背过“TCP 粘包”却说不清 UDP 如何防丢包的候选人以及被“两台电脑 UDP 通信用网络调试助手连不通”卡住一整天的嵌入式联调同学。接下来所有内容全部来自我们线上灰度环境的真实配置、Wireshark 抓包分析截图、JVM GC 日志片段以及被客户凌晨三点电话叫醒后改出来的第 7 版UdpServerHandler。2. 整体架构设计与核心选型逻辑为什么不用 Spring Integration UDP为什么坚持用 NIO 而非 EPOLL2.1 不选 Spring Integration UDP 的三个硬伤很多 Spring Boot 开发者第一反应是“Spring 官方不是有UdpInboundGateway吗直接配个Bean不就完了”——我试过而且在线上跑过两周最后删得比写得还快。原因很实在第一线程模型不可控。UdpInboundGateway底层封装的是DatagramSocket它默认使用SimpleAsyncTaskExecutor每次receive()都新建线程。当设备心跳包频率升到 10Hz单机 5000 台设备时线程数瞬间飙到 5 万JVM 直接 OOM。你没法像 Netty 那样指定EventLoopGroup的线程数并复用。第二无内存池管理。它把DatagramPacket的byte[]直接扔给业务逻辑而byte[]是堆内对象。我们实测过每秒 2 万次 128 字节包GC Young Gen 每 3 秒触发一次ParNew时间平均 80ms延迟毛刺直接破 500ms。Netty 的PooledByteBufAllocator可以复用 Direct Buffer把这部分开销压到微秒级。第三Pipeline 缺失导致协议耦合严重。UdpInboundGateway只提供MessageConverter接口你得自己写fromBytes()和toBytes()但设备报文往往带 CRC 校验、变长字段、TLV 结构这些逻辑如果全塞进 converter代码会变成“if-else 泥潭”。而 Netty 的ChannelPipeline允许你分层UdpChecksumHandler→UdpTlvDecoder→UdpDeviceAuthHandler→UdpBusinessHandler每一层只干一件事单元测试好写线上出问题也能快速定位在哪一层挂了。提示如果你只是做内部工具、日志上报这类低频 UDP 场景UdpInboundGateway完全够用。但凡涉及设备直连、实时指令下发、QoS 要求 99.9%请立刻切换到 Netty。2.2 NIO vs EPOLL为什么在 Linux 生产环境仍首选 NIONetty 支持NioDatagramChannel和EpollDatagramChannel两种传输实现。网上很多教程鼓吹“EPOLL 性能吊打 NIO”但我们线上集群CentOS 7.9 Kernel 5.10压测结果恰恰相反在 10G 网卡、单机 2 万并发 UDP 连接下NioDatagramChannel的 P99 延迟稳定在 12ms而EpollDatagramChannel在流量突增时会出现 300ms 的尖峰。原因在于EPOLL 的epoll_wait()对 UDP 的适配存在固有缺陷。TCP 是流式协议epoll_wait()可以精准返回“有数据可读”的 socket但 UDP 是报文式协议内核 socket buffer 里可能堆积上百个包epoll_wait()只告诉你“这个 fd 有数据”却不告诉你有多少个包、每个包多大。Netty 的EpollDatagramChannel为避免饥饿必须在一次事件循环中尽可能多地recv()这就导致单次eventLoop执行时间不可控影响其他 channel 的调度。NIO 的Selector虽然有“惊群”问题但 Netty 已做了深度优化。从 Netty 4.1.42 开始NioEventLoop引入了selectNow()wakeup()的混合策略空轮询时主动selectNow()触发一次无阻塞检查避免select()长时间阻塞同时wakeup()调用被优化为Unsafe指令开销极低。我们在压测中观察到NioEventLoop的 CPU 占用曲线非常平滑而EpollEventLoop在流量高峰时会出现周期性 20% 的 CPU 尖峰。NIO 的兼容性是硬需求。我们的部分边缘节点运行在国产化 ARM 服务器麒麟 V10 鲲鹏 920上内核未开启epoll支持EpollDatagramChannel直接抛UnsatisfiedLinkError。而NioDatagramChannel是纯 Java 实现零依赖部署即用。所以我们的选型结论很明确除非你明确知道自己的业务是“超低延迟 超高吞吐 全量可控内核参数”否则NioDatagramChannel是更稳、更省心、更易排障的选择。这也是为什么我们线上所有 UDP 服务bootstrap.channel(NioDatagramChannel.class)这行代码写了上百遍从未动过。2.3 EventLoopGroup 的线程数怎么定不是越多越好EventLoopGroup是 Netty 的心脏但它的线程数配置是最大误区来源。很多人看到“高性能”就盲目设成Runtime.getRuntime().availableProcessors() * 2结果发现 CPU 占用 90%、延迟飙升。真相是UDP 的EventLoopGroup线程数应该由“单线程能处理的最大报文吞吐量”决定而不是 CPU 核数。我们做过一组对照实验同一台 16 核服务器分别用 4/8/16/32 个线程的NioEventLoopGroup启动 UDP 服务持续发送 100 字节心跳包测量 P99 延迟和 GC 次数EventLoop 线程数P99 延迟 (ms)Young GC 次数/分钟CPU 平均占用418.21235%811.5842%1612.1958%3215.71576%最优解出现在 8 线程。原因在于每个NioEventLoop线程要处理Selector.select()、recv()、decode()、handler()四个阶段。当线程数过多Selector的锁竞争加剧SelectorImpl内部有publicKeys和selectedKeys两个HashSet多线程操作需加锁反而拖慢整体吞吐。而 8 线程刚好匹配我们单核能稳定处理 2500 QPS 的实测能力16 核 × 2500 4 万 QPS覆盖业务峰值 3.5 万。实操心得线程数公式建议为min(8, Runtime.getRuntime().availableProcessors())。如果你的服务器是 32 核以上也不要盲目加到 16先用 8 线程压测看NioEventLoop的pendingTasks是否持续 1000可通过 JMX 查看io.netty.channel.nio.NioEventLoop#pendingTasks再逐步增加。3. 核心细节解析与实操要点从启动到上线每个环节的生死线3.1 UDP Server 启动的 5 个关键配置项少一个都可能上线即故障Netty UDP 服务的启动代码看似简单但Bootstrap的每个.option()都是血泪教训换来的。下面这 5 个配置是我们线上所有 UDP 服务的标配缺一不可Bootstrap bootstrap new Bootstrap(); bootstrap.group(new NioEventLoopGroup(8)) // ① 显式指定线程数不依赖默认值 .channel(NioDatagramChannel.class) .option(ChannelOption.SO_BROADCAST, true) // ② 必须开启广播否则局域网设备发现失败 .option(ChannelOption.SO_RCVBUF, 1024 * 1024) // ③ 接收缓冲区设为 1MB防突发流量丢包 .option(ChannelOption.SO_SNDBUF, 1024 * 1024) // ④ 发送缓冲区同理避免 send() 阻塞 .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // ⑤ 强制使用内存池 .handler(new ChannelInitializerNioDatagramChannel() { Override protected void initChannel(NioDatagramChannel ch) throws Exception { ChannelPipeline p ch.pipeline(); p.addLast(new UdpChecksumHandler()); // 校验层 p.addLast(new UdpTlvDecoder()); // 解析层 p.addLast(new UdpDeviceAuthHandler()); // 认证层 p.addLast(new UdpBusinessHandler()); // 业务层 } });逐条解释其不可替代性①group(new NioEventLoopGroup(8))必须显式传入EventLoopGroup实例。如果写成group(new NioEventLoopGroup())Netty 会用Runtime.getRuntime().availableProcessors() * 2创建线程线上 32 核机器直接起 64 个线程Selector锁竞争让延迟翻倍。我们曾因此被客户投诉“设备心跳超时率 15%”查了三天才发现是这里。②SO_BROADCAST, true这是 UDP 设备自动发现的基石。很多 IoT 设备如 Zigbee 网关启动时会向255.255.255.255:9999发送广播包宣告自身存在。如果服务端没开广播永远收不到这第一声“你好”。注意生产环境防火墙需放行 UDP 广播iptables -A INPUT -p udp -d 255.255.255.255 -j ACCEPT。③SO_RCVBUF, 1024 * 1024接收缓冲区大小。Linux 默认是 212992 字节约 208KB在 10G 网卡下千兆网卡满速时 1 秒能塞 125MB 数据缓冲区瞬间溢出内核直接丢包。设为 1MB 是经过我们压测验证的安全值在 2 万设备 30 秒心跳约 666 QPS下缓冲区占用率峰值 40%。④SO_SNDBUF, 1024 * 1024发送缓冲区同理。当服务端需要批量回复 ACK 或下发指令时如果缓冲区太小channel.writeAndFlush()会阻塞拖慢整个EventLoop。我们曾遇到过因SO_SNDBUF过小导致指令下发延迟从 5ms 涨到 200ms 的事故。⑤ALLOCATOR, PooledByteBufAllocator.DEFAULT强制内存池。PooledByteBufAllocator会预分配 Direct Buffer 内存块并用Recycler对象池管理ByteBuf实例。实测对比用UnpooledByteBufAllocator时每秒 1 万包 GC 频率 12 次用Pooled后降为 0.3 次ParNew时间从 60ms 降到 2ms。注意SO_RCVBUF和SO_SNDBUF的值不能瞎设。Linux 内核有net.core.rmem_max和net.core.wmem_max限制设太大内核会静默截断。执行sysctl net.core.rmem_max查看上限我们的生产服务器已调为1677721616MB。3.2 UDP 的“粘包”不存在那为什么还要处理“半包”这是 Netty UDP 最大的认知陷阱。“TCP 粘包”是经典问题但很多人误以为“UDP 不会粘包所以不用处理”。错UDP 不会“粘”但会“断”——当报文长度超过 MTU通常 1500 字节IP 层会自动分片而 UDP 层完全感知不到。你的DatagramChannel收到的可能是某个大报文的第 2 片也可能是第 1 片和第 3 片的组合因为 IP 分片可能乱序到达。这就是“半包”。我们的真实案例某安防摄像头用 UDP 上传 H.264 关键帧I 帧单帧 2800 字节。网络路径中某台交换机 MTU 为 1400IP 层将其分为 2 片1400 1400。但第二片在网络中延迟了 50ms先到的第一片被UdpTlvDecoder当作完整报文解析CRC 校验失败直接丢弃。结果就是视频花屏、关键帧丢失。解决方案是在ChannelPipeline中加入UdpFragmentHandler基于 IP 分片标识IPv4 的Identification字段 FlagsFragment Offset重组分片。Netty 本身不提供此功能需自行实现。核心逻辑如下维护一个ConcurrentHashMapShort, FragmentBufferkey 是 IP 包的Identification字段16 位value 是FragmentBuffer对象包含ListFragment和int totalLength。每收到一个 UDP 包先解析 IP 头若Flags 0x01 ! 0MF 标志置位或Fragment Offset ! 0则为分片包存入对应FragmentBuffer。当FragmentBuffer中所有分片收齐sum(fragment.length) totalLength合并为完整ByteBuf传递给下一层。设置超时清理每个FragmentBuffer附带expireTime System.nanoTime() 100_000_000L100ms超时未收齐则丢弃防内存泄漏。实操心得分片重组必须在ChannelHandler的channelRead()中完成不能放到业务线程池。因为分片属于同一 IP 包必须由同一个EventLoop处理否则ConcurrentHashMap的并发安全无法保证。我们曾因把重组逻辑放到Async方法里导致分片被不同线程处理FragmentBuffer状态错乱内存暴涨。3.3 如何让 UDP 具备“连接感”DeviceContext 的生命周期管理UDP 无连接但业务需要“连接感”设备上线要初始化上下文心跳超时要清理资源指令下发要找到目标设备。如果每次channelRead()都去数据库查设备信息QPS 上不去如果全放内存又怕 OOM。我们的方案是用Channel.attr()绑定DeviceContext并配合IdleStateHandler实现自动生命周期管理。// 在 ChannelInitializer 中添加 p.addLast(new IdleStateHandler(0, 0, 30)); // 读空闲 30 秒触发事件 p.addLast(new UdpDeviceContextHandler()); // 自定义 handler处理 IdleStateEventUdpDeviceContextHandler的核心逻辑channelActive()时从DatagramPacket解析出设备 MAC 或 SN查询缓存获取DeviceContext存入channel.attr(DEVICE_CTX_KEY).set(ctx)。userEventTriggered()监听IdleStateEvent若state READER_IDLE说明 30 秒没收到心跳调用ctx.onTimeout()清理资源如关闭 MQTT 会话、释放内存缓存然后channel.close()。channelInactive()时确保DeviceContext的onClose()被调用释放所有关联资源。这样每个NioDatagramChannel实例就成为一个轻量级“虚拟连接”DeviceContext的创建、保活、销毁全部由 Netty 生命周期驱动无需额外定时任务。我们线上单机管理 5 万台设备DeviceContext对象平均生命周期 2.3 小时内存占用稳定在 1.2GBGC 压力极小。提示DeviceContext中不要存大对象如图片缓存、音频流。我们曾把设备最新抓拍图存进DeviceContext结果 5 万台设备每台存 100KB 图片内存直接爆到 500GB。正确做法是存deviceId - cacheKey映射图片走 Redis 或本地文件系统。4. 实操过程与核心环节实现从 Wireshark 抓包到线上压测的完整闭环4.1 用 Wireshark 精准定位 UDP 丢包不只是看“no response”Wireshark 是 UDP 排查的黄金标准但很多人只会过滤udp.port 9999然后看有没有回包。这远远不够。真正的丢包定位需要三层分析第一层IP 层分片分析过滤表达式ip.flags.mf 1 or ip.frag_offset ! 0作用找出所有被分片的包。如果服务端收不到完整报文先看这里是否大量分片且分片顺序错乱ip.frag_offset不连续。我们曾发现某运营商网络对大于 1200 字节的 UDP 包强制分片且第二片丢包率高达 8%最终通过SO_RCVBUF调大 应用层分包解决。第二层UDP 层校验和分析过滤表达式udp.checksum_bad 1作用识别因网络干扰导致的校验失败。UDP 校验和是可选的但 Netty 默认开启。如果大量checksum_bad说明物理链路有问题网线老化、光衰过大需联系网络运维。第三层应用层时序分析这是最关键的一步。UDP 无序号但我们可以用业务字段构造逻辑时序。例如设备心跳包中有一个seq字段4 字节递增。在 Wireshark 中右键该字段 → “Apply as Column”新增一列显示seq。然后按此列排序观察是否出现跳变如 100 → 105跳变超过阈值如 3即判定为丢包。我们用此法准确定位出某款 4G 模组在弱信号下seq会重复发送导致服务端误判为重放攻击。实操技巧Wireshark 的“IO Graphs”功能可画出 UDP 流量热力图。设置 Y 轴为udp.lengthX 轴为时间能直观看到“突发大包潮”——这往往是设备固件 bug 的征兆如内存泄漏导致缓存积压后一次性爆发。4.2 用 iperf3 精准打流不只是测带宽更要测 UDP 的稳定性iperf3 -u -c server -b 100M -t 60是常见命令但它只告诉你“带宽 95Mbps”对 UDP 服务毫无意义。真正有用的参数组合是# 模拟真实设备心跳每秒 1000 个 128 字节包持续 5 分钟 iperf3 -u -c 192.168.1.100 -b 1000K -l 128 -t 300 -i 10 --udp-write-timeout1000000 # 关键参数解读 # -b 1000K目标速率 1000Kbps ≈ 1000 * 1024 / 8 / 128 ≈ 1000 包/秒 # -l 128包长 128 字节匹配设备心跳包 # -i 10每 10 秒输出一次统计观察波动 # --udp-write-timeout1000000写超时 1 秒防客户端卡死关注输出中的三个核心指标Jitter (ms)抖动值。TCP 服务可以忽略但 UDP 必须 5ms。如果 jitter 20ms说明网络拥塞或服务端处理不及时。我们曾因此发现UdpBusinessHandler中一个同步 Redis 查询耗时 15ms拖垮了整个 pipeline。Lost/Total丢包率。线上服务要求 0.1%。如果iperf3显示丢包先检查服务端SO_RCVBUF是否足够再用ss -uln查看Recv-Q是否持续 0表示内核缓冲区满。Datagrams实际发送包数。如果远小于理论值1000 包/秒 × 300 秒 30 万说明客户端网卡或驱动有问题。我们遇到过某品牌笔记本无线网卡在 UDP 高频发送时驱动会主动限速。注意iperf3的-b参数是“尽力而为”不是硬限速。要精确控制发包速率需用tcTraffic Control在客户端限速tc qdisc add dev wlan0 root tbf rate 1000kbit burst 32kbit latency 700ms。4.3 Spring Boot 3.x Netty MQTT 的物联网实战充电桩指令下发的 7 步落地“springboot 3.x netty mqtt 实战物联网智能充电桩”是热搜词也是我们刚交付的项目。核心诉求充电桩上报状态UDP后台下发充电指令MQTT要求指令 5 秒内触达。难点在于UDP 和 MQTT 是异构协议如何保证指令不丢失、不错发、可追溯我们走了 7 步Step 1定义统一设备标识放弃用 IPPort 作为设备 IDUDP 无连接IP 可能变采用vendorId deviceId组合如tesla_charger_001存入 Redis 的device:onlineSet。设备上线时SADD device:online tesla_charger_001心跳超时SREM。Step 2UDP 接收层透传设备 IDUdpTlvDecoder解析出deviceId后不走业务逻辑直接存入channel.attr(DEVICE_ID_KEY)并转发给MqttCommandRouter。Step 3构建 MQTT 指令路由中心Component public class MqttCommandRouter { private final MapString, CompletableFutureMqttCommandResult pendingCommands new ConcurrentHashMap(); public CompletableFutureMqttCommandResult sendCommand(String deviceId, MqttCommand cmd) { String reqId UUID.randomUUID().toString(); CompletableFutureMqttCommandResult future new CompletableFuture(); pendingCommands.put(reqId, future); // 发布 MQTT 指令主题为 command/{deviceId}/{reqId} mqttClient.publish(command/ deviceId / reqId, cmd.toJson().getBytes()); return future; } }Step 4MQTT 订阅指令响应主题服务端订阅response//#收到响应后提取reqIdcomplete()对应的CompletableFuture。Step 5UDP 层注入指令超时机制UdpBusinessHandler中收到设备状态后调用MqttCommandRouter.sendCommand()并设置future.orTimeout(5, TimeUnit.SECONDS)。超时则记录告警触发人工介入。Step 6指令幂等与重试MQTT 指令消息体包含reqId和timestamp。充电桩固件收到指令后先查本地lastReqId缓存若重复则直接 ACK不执行。服务端对超时指令最多重试 2 次间隔 1s重试时reqId不变。Step 7全链路追踪埋点在UdpBusinessHandler.channelRead()、MqttCommandRouter.sendCommand()、MQTT callback三个节点打日志用traceId串联。日志格式[traceId] [deviceId] [step] [status] [costMs]。线上问题 3 分钟内可定位到是 UDP 解析失败还是 MQTT 网络超时还是充电桩固件未响应。这套方案上线后指令端到端成功率 99.992%P95 延迟 2.3 秒完全满足 SLA。5. 常见问题与排查技巧实录那些让你凌晨三点爬起来的 UDP 坑5.1 “两台电脑 UDP 通信使用网络调试助手死活连不通” —— 90% 是这 5 个原因这是最常被问的问题。我们整理了 100 次远程协助记录按发生概率排序排名原因检查方法解决方案1防火墙拦截sudo ufw statusUbuntu或Windows Defender 防火墙高级设置添加入站规则协议 UDP端口 XXX作用域192.168.1.0/242绑定地址错误服务端代码bootstrap.bind(0.0.0.0:9999)vs127.0.0.1:9999必须用0.0.0.0127.0.0.1只监听本机回环3网络调试助手未选对模式助手界面看“本地地址”是否填了0.0.0.0而非空或127.0.0.1填0.0.0.0端口与服务端一致4跨网段未配路由A 电脑192.168.1.100B 电脑10.0.0.100ping不通用route add 10.0.0.0 mask 255.0.0.0 192.168.1.1添加静态路由5SELinux 强制访问控制CentOS/RHEL 执行sestatus输出enabledsudo setsebool -P nis_enabled 1或临时sudo setenforce 0实操心得第一步永远是telnet或nc测试端口通不通。nc -uvz 192.168.1.100 9999如果显示Connection to 192.168.1.100 9999 port [udp/9999] succeeded!说明网络层通畅问题一定在应用层代码或助手配置。5.2 “nacos 中有用到 netty 吗”—— 深挖 Nacos 2.x 的 UDP 心跳机制Nacos 2.x 确实用了 Netty UDP但仅用于服务实例的心跳探测Health Check而非主注册协议。其原理是Nacos Server 启动时创建NioEventLoopGroup和NioDatagramChannel监听7848端口可配。Client SDK 每 5 秒向 Server 的7848端口发送一个 UDP 包内容为instanceId timestamp checksum。Server 收到后不回复只更新该实例的lastHeartbeatTime。如果 15 秒未收到则标记为不健康。为什么用 UDP因为心跳是“尽力而为”的探测不需要 TCP 的可靠传输UDP 的轻量性能支撑百万级实例心跳。但注意Nacos 的服务发现、配置推送等核心功能依然走 HTTP/gRPCTCP。UDP 只是健康检查的补充不影响主流程。我们曾因误以为 Nacos 用 UDP 传服务列表导致在防火墙只开了7848端口结果服务调用全部失败。正确做法是8848HTTP、9848gRPC、7848UDP 心跳三个端口全开。5.3 “查看电脑关闭 udp 服务”—— 本质是查哪些进程占用了 UDP 端口Windows 下执行netstat -ano -p UDP | findstr :9999 # 输出类似UDP 0.0.0.0:9999 *:* 12345 # 然后查 PID 12345 对应的进程tasklist | findstr 12345Linux 下执行sudo lsof -iUDP:9999 # 或 sudo ss -ulpn | grep :9999如果发现是systemd或avahi-daemon占用了端口不要强行 kill而是修改你的服务端口。avahi-daemon是 Zeroconf 服务关闭它会影响局域网设备发现。避坑技巧开发时用0.0.0.0:0让系统自动分配空闲端口打印日志Started UDP server on /0.0.0.0:XXXX避免端口冲突。线上再固定端口。5.4 “netty websocket 怎么做鉴权”—— UDP 和 WebSocket 是两套体系别混了这是典型的概念混淆。WebSocket 是基于 TCP 的应用层协议而 UDP 是网络层协议二者无直接关系。Netty 中WebSocketServerProtocolHandler只能用在NioSocketChannelTCP上无法用于 NioDatagramChannel
返回列表