Netty多通道多对话管理
- 多通道:基于不同用途,服务器会建立多个socket监听,因此用户连接服务器的时候也会连接多个socket
- 多对话:一个用户连接服务器成功则建立一个对话,如果有多个用户则可能建立多个对话
需要解决的问题有3个:
数据隔离
先上结论:
- 线程隔离(不可行):同会话读取数据会出现线程切换,这是Netty基于事件驱动的模型的特性导致的
- 会话隔离(可行):对于单通道业务可行,但是对于多通道业务,无法实现同一用户的数据共享
- 身份隔离(可行):适配各种业务场景,但是身份传输和管理机制相对复杂
-
线程隔离(不可行)
使用ThreadLocal存储用户数据,当出现线程切换的时候,用户数据丢失
-
会话隔离(可行)
有两种实现方式:使用ChannelId、使用ChannelHandlerContext的属性Attribute
- 使用ChannelId
public class YourHandler extends ChannelHandlerAdapter {
private Map<ChannelId, YourSessionData> sessionDataMap = new ConcurrentHashMap<>();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelId channelId = ctx.channel().id();
YourSessionData sessionData = sessionDataMap.computeIfAbsent(channelId, k -> new YourSessionData());
// 使用sessionData处理数据
}
}
- 使用ChannelHandlerContext的属性Attribute
public class YourHandler extends ChannelHandlerAdapter {
private AttributeKey<YourSessionData> sessionDataKey = AttributeKey.valueOf("attName");
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Attribute<YourSessionData> attr = ctx.attr(sessionDataKey);
YourSessionData sessionData = attr.get();
if (sessionData == null) {
sessionData = new YourSessionData();
attr.set(sessionData);
}
// 使用sessionData处理数据
}
}
- 身份隔离(可行)
由用户端发起身份认证获取身份ID,并将身份ID用于存储会话数据的标识。
- 搭建用户登录服务(这里依然使用Netty,但是实际上其他任意方式实现都可行,如使用Spring搭建Http接口)
public class LoginCenterHandler extends ChannelHandlerAdapter {
/**
* 为了简便,用户身份信息验证过程略,并使用UUID作为用户登录id
* 实际使用过程中需要在channelRead方法中接收用户身份信息并验证,验证通过后保存用户登录状态
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
UUID uuid = UUID.randomUUID();
byte[] bytes = UUIDUtils.toBytes(uuid);//uuid转16位字节数组,过程略
ByteBuf buf = Unpooled.wrappedBuffer(bytes);
final ChannelFuture f = ctx.writeAndFlush(buf);
//保存用户登录状态略
//登录成功后关闭通道
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close();
}
});
}
}
用户身份识别
- 在需要验证身