Skip to content

结论先说: 这个项目里二进制文件根本不经过 Netty 。图片/语音/视频/文件全部走 HTTP 上传到对象存储,Netty 长连接只传"带 URL 的 JSON 文本消息" ——这是 IM 系统经典的“信令与媒体分离”架构。看下图。

思考过程

代码链路 ​

① 前端上传(HTTP,与 Netty 无关)

ChatBox.vue#L482-L533 :选图/选文件后先FormData +multipart/form-data POST 到 im-platform:

  • 图片 →/image/upload (返回originUrl 、thumbUrl 、宽高)
  • 文件 →/file/upload (返回 url) ② 后端接收与存储

FileController.java 用 Spring MVC 的MultipartFile 接收(Tomcat 的 HTTP 栈,不占 Netty 线程), FileServiceImpl.java 处理:

  • 文件名长度、大小校验(≤20M)、图片格式校验
  • MD5 秒传 :算出文件 md5 查im_file_info 表,命中直接复用已有 URL,不再重复上传
  • MinioService 上传到 MinIO 对象存储(bucket 设为 public,URL 可直接访问)
  • 图片超过 50K 时额外压缩生成缩略图;元信息记入im_file_info 表;非永久文件由 FileExpireTask 定期清理 ③④ 经 Netty 发“指针”消息

上传成功后,前端把 URL 塞进消息content ,走 WebSocket 发送——这就是上一轮讲的完整链路。消息类型对应数据库里的type 字段(1:图片 2:文件 3:语音 4:视频), 但无论哪种,content 都是 JSON 文本 ,图片消息大致长这样:

json
{ "type": 1, "content": "{\"originUrl\":\"http://minio:9000/im/image/xx.jpg\",\"thumbUrl\":\"http://minio:9000/im/image/xx_thumb.jpg\",\"width\":1080,\"height\":1920}" }

Netty 侧的ReplayingDecoder /TextWebSocketFrame 处理的是这几十个字节的 JSON, 与一张 20M 的原图毫无关系 。语音消息(前端 recorder 录完)、视频文件同理;实时音视频通话则完全走 WebRTC,Netty 只转发信令。

为什么不直接在 Netty 里传二进制 ​

  1. 效率 :WebSocket 文本帧传二进制要 Base64(体积 +33%);TCP 自定义协议虽可传原始字节,但要自己解决大文件分片、断点、限流、内存背压
  2. 稳定性 :本项目 Decoder 是“按长度一次readBytes 整包”的简单模型,大文件会瞬间在ByteBuf 里堆出几十 M,挤压 worker 线程内存甚至拖垮事件循环
  3. 架构收益 :消息表content 只存 URL 保持轻量;MinIO 天然提供直传、CDN、生命周期管理;Netty 网关保持“纯信令”职责单一

如果真的要用 Netty 传二进制(通用做法) ​

  • WebSocket 通道 :改用BinaryWebSocketFrame ,Decoder 写成MessageToMessageDecoder<BinaryWebSocketFrame> 直接操作ByteBuf
  • TCP 通道 :自定义协议升级为魔数 + 消息类型 + 分片序号 + 长度 + 数据 ,控制帧与数据帧分型,接收端用CompositeByteBuf 零拷贝拼装分片,配合内存水位(ChannelConfig.setWriteBufferWaterMark )做背压
  • 大文件下发 :Netty 的ChunkedWriteHandler +FileRegion (底层sendfile 零拷贝)流式写文件——本项目 ws Pipeline 里其实挂了ChunkedWriteHandler ,但只用于常规响应分块,并未传文件
  • Netty 写 HTTP 文件服务 :用HttpPostRequestDecoder +HttpDataFactory 解析 multipart 上传——等于是自己用 Netty 重写了一遍 Spring MVC + MinIO 已经做完的事,没有必要

一句话总结: Netty 只当“邮差”,传的是取件地址(URL),包裹(二进制)永远走 HTTP + 对象存储这条专用物流。

Released under the MIT License.