写一个 Netty 玩具

基本步骤

案例与数据结构

这里 Message Body 使用 JSON 格式,当然也能用其他的格式。

编码

  1. 首先要解决粘包、半包问题。
  2. 接着二次解码,处理各种格式的包内容。
  3. 然后是具体的 handler 方法编写。
  4. 然后是服务器端、客户端内容。值得注意的是对于服务端的逻辑流程与客户端相反,只要注意 request 和 response 的填写即可。
  5. MessageToMessageEncoder 中会自动匹配类型,类型不符合会在 pipline 中跳过。

完善客户端

请求与请求之间不应该有联系:

为了达到上图的目的,我们需要有 id-future 的关系,在 request 里 new 一个 future ,保存关系,然后用 future.get() 来等待结果,结果带有相同的 id

思考

  • LengthFieldBasedFrameDecoderinitialBytesToStrip 未考虑设置;
    • 如果不设置,结果含有 length 字段,会解析失败。
    • 默认该值为 0 ,但是不一定正确。
  • ChannelHandler 顺序不正确,ChannelHandler 该共享不共享,不该共享却共享;
    • pipline 里的顺序非常重要,先解码,再处理,再编码,注意编码穿插在解码后。
  • 分配 ByteBuf :分配器直接用 ByteBufAllocator.DEFAULT 等,而不是采用 ChannelHandlerContext.alloc()
    • 大多数情况没有问题,ctx.alloc() 是可以切换堆外、堆内等情况的,如果换了实现,ByteBufAllocator.DEFAULT 就不支持了。
  • 未考虑 ByteBuf 的释放;
    • 在 Handler 中使用 SimpleChannelInboundHandler 的好处就是有内置的释放 ByteBuf ,即 release() 方法。
  • 错以为 ChannelHandlerContext.write(msg) 就写出数据了;
    • write 仅仅是加到队列而不是发出去。
  • 乱用 ChannelHandlerContext.channel().writeAndFlush(msg)
    • 还有一种 ctx.channel().writeAndFlush(msg) 是完整的 pipline 重新走一遍(客户端可能会用),而 ctx.writeAndFlush() 仅仅是在当前 pipline 的位置寻找下一个可以走的位置(服务端常用)。