写一个 Netty 玩具
基本步骤
案例与数据结构
这里 Message Body 使用 JSON 格式,当然也能用其他的格式。
编码
- 首先要解决粘包、半包问题。
- 接着二次解码,处理各种格式的包内容。
- 然后是具体的 handler 方法编写。
- 然后是服务器端、客户端内容。值得注意的是对于服务端的逻辑流程与客户端相反,只要注意 request 和 response 的填写即可。
MessageToMessageEncoder
中会自动匹配类型,类型不符合会在 pipline 中跳过。
完善客户端
请求与请求之间不应该有联系:
为了达到上图的目的,我们需要有 id-future
的关系,在 request
里 new 一个 future
,保存关系,然后用 future.get()
来等待结果,结果带有相同的 id
。
思考
LengthFieldBasedFrameDecoder
中initialBytesToStrip
未考虑设置;- 如果不设置,结果含有
length
字段,会解析失败。 - 默认该值为 0 ,但是不一定正确。
- 如果不设置,结果含有
ChannelHandler
顺序不正确,ChannelHandler
该共享不共享,不该共享却共享;pipline
里的顺序非常重要,先解码,再处理,再编码,注意编码穿插在解码后。
- 分配
ByteBuf
:分配器直接用ByteBufAllocator.DEFAULT
等,而不是采用ChannelHandlerContext.alloc()
;- 大多数情况没有问题,
ctx.alloc()
是可以切换堆外、堆内等情况的,如果换了实现,ByteBufAllocator.DEFAULT
就不支持了。
- 大多数情况没有问题,
- 未考虑
ByteBuf
的释放;- 在 Handler 中使用
SimpleChannelInboundHandler
的好处就是有内置的释放ByteBuf
,即release()
方法。
- 在 Handler 中使用
- 错以为
ChannelHandlerContext.write(msg)
就写出数据了;write
仅仅是加到队列而不是发出去。
- 乱用
ChannelHandlerContext.channel().writeAndFlush(msg)
;- 还有一种
ctx.channel().writeAndFlush(msg)
是完整的 pipline 重新走一遍(客户端可能会用),而ctx.writeAndFlush()
仅仅是在当前 pipline 的位置寻找下一个可以走的位置(服务端常用)。
- 还有一种