Netty入门与实战:仿写微信 IM 即时通讯系统(三)(停更ing)
源码地址及更新日志
https://delaunay.coding.net/p/netty-demo/d/netty-demo/git
实战:使用 channelHandler 的热插拔实现客户端身份校验
身份检验
首先,我们在客户端登录成功之后,标记当前的 channel 的状态为已登录
在登录成功之后,我们通过给 channel 打上属性标记的方式,标记这个 channel 已成功登录
判断一个用户是否登录很简单,只需要调用一下 LoginUtil.hasLogin(channel) 即可,但是,Netty 的 pipeline 机制帮我们省去了重复添加同一段逻辑的烦恼,我们只需要在后续所有的指令处理 handler 之前插入一个用户认证 handler:AuthHandler
在 MessageRequestHandler
之前插入了一个 AuthHandler
,因此 MessageRequestHandler
以及后续所有指令相关的 handler(后面小节会逐个添加)的处理都会经过 AuthHandler
的一层过滤,只要在 AuthHandler
里面处理掉身份认证相关的逻辑,后续所有的 handler 都不用操心身份认证这个逻辑,接下来我们来看一下 AuthHandler
的具体实现:
1 | public class AuthHandler extends ChannelInboundHandlerAdapter { |
AuthHandler
继承自ChannelInboundHandlerAdapter
,覆盖了channelRead()
方法,表明他可以处理所有类型的数据- 在
channelRead()
方法里面,在决定是否把读到的数据传递到后续指令处理器之前,首先会判断是否登录成功,如果未登录,直接强制关闭连接(实际生产环境可能逻辑要复杂些,这里我们的重心在于学习 Netty,这里就粗暴些),否则,就把读到的数据向下传递,传递给后续指令处理器。
如果客户端已经登录成功了,那么在每次处理客户端数据之前,我们都要经历这么一段逻辑,比如,平均每次用户登录之后发送100次消息,其实剩余的 99 次身份校验逻辑都是没有必要的,因为只要连接未断开,客户端只要成功登录过,后续就不需要再进行客户端的身份校验。
移除校验逻辑
在客户端校验通过之后,我们不再需要 AuthHandler 这段逻辑
1 | public class AuthHandler extends ChannelInboundHandlerAdapter { |
上面的代码中,判断如果已经经过权限认证,那么就直接调用 pipeline 的 remove() 方法删除自身,这里的 this 指的其实就是 AuthHandler 这个对象,删除之后,这条客户端连接的逻辑链中就不再有这段逻辑了。
身份校验演示
在演示之前,对于客户端侧的代码,我们先把客户端向服务端发送消息的逻辑中,每次都判断是否登录的逻辑去掉,这样我们就可以在客户端未登录的情况下向服务端发送消息
NettyClient.java
1 | private static void startConsoleThread(Channel channel) { |
有身份认证的演示
先启动服务端,再启动客户端,在客户端的控制台,我们输入消息发送至服务端,这个时候服务端与客户端控制台的输出分别为
观察服务端侧的控制台,我们可以看到,在客户端第一次发来消息的时候, AuthHandler 判断当前用户已通过身份认证,直接移除掉自身,移除掉之后,回调 handlerRemoved
无身份认证的演示
我们到 LoginResponseHandler 中,删除发送登录指令的逻辑:
1 | public class LoginResponseHandler extends SimpleChannelInboundHandler<LoginResponsePacket> { |
客户端向服务端写登录指令的逻辑进行删除,然后覆盖一下 channelInactive() 方法,用于验证客户端连接是否会被关闭。
接下来,我们先运行服务端,再运行客户端,并且在客户端的控制台输入文本之后发送给服务端
这个时候服务端与客户端控制台的输出分别为:
由此看到,客户端如果第一个指令为非登录指令,AuthHandler 直接将客户端连接关闭,并且,从上小节,我们学到的有关 ChannelHandler 的生命周期相关的内容中也可以看到,服务端侧的 handlerRemoved() 方法和客户端侧代码的 channelInActive() 会被回调到。
总结
- 如果有很多业务逻辑的 handler 都要进行某些相同的操作,我们完全可以抽取出一个 handler 来单独处理
- 如果某一个独立的逻辑在执行几次之后(这里是一次)不需要再执行了,那么我们可以通过 ChannelHandler 的热插拔机制来实现动态删除逻辑,应用程序性能处理更为高效