channel.php id,项目系统Netty的Channel和用户之间的关系绑定正确做法,以及Channel通道的安全性方案...

前言

考虑一个功能业务,在web程序中向指定的某个用户进行实时通讯

在Web运用的Socket通讯功能中(如在线客服),为保证点对点通讯.而这个看似简单的根据用户寻到起channel通道实际会碰到不少问题

web程序中的Http协议是无状态的

一般项目中socket服务和web项目是独立部署的

socket连接存在重连的情况,而Channel对象每次都不一样

Channel是面向网卡绑定的,无法序列化

解决方案

通过管理一个线程安全的用户标识(如用户主键)和对应channel的map链表

private final ConcurrentHashMap channelMap = new ConcurrentHashMap<>();

那么问题来了,

在netty模块中怎么得到这个用户标识?

又如何保证netty socket模块可以安全的识别某个通道属于某个用户?(这个可以像上面一样的方式解决)

netty socket模块接收到一条消息又任何证明这条通道是可信的?

在netty的实现中是没有认证也没有HttpSession这个东西的,也就是说.在netty程序线程中是无法得到web项目登录的用户情况的.

出于这点,参考web项目集群的session共享方案.可以在Redis等缓存中保存登录信息.

6204d50c77392ebc171f18020cda706c.png

在web项目中登录之后在redis中在这个以用户id为名的key中保存一个token,

在客户端socket通道建立之后立马发送包含一个用户标识和ASK到socket服务端,

服务端根据ASK计算一个token和redis比对.一旦比对成功,则绑定当前channel和用户之间的关系;

之后server每接收到一条消息就检测当前通道有没有绑定用户信息

这个key是一次性的.这点非常重要,试想一下.在你前台项目可能因为cookie过期或者后台已经自动将该用户下线,而你的用户标识和ASK暴露.那么就可能被恶意连接发送消息;

另外关于token和ASK之类的验证传输如果仅仅是为了识别和绑定用户与channel的关系,这点也是可以忽略的,只要redis中保存该用户的登录状态即可,通道建立的第一次通讯就传输当前浏览器的登录用户标识,再去redis中比对即可,但是redis中的这个key还是一次性的好,避免一个用户建立多条socket通道

正确的绑定通道Channel和用户之间的关系

如果我们仅仅有一个ConcurrentHashMap,是无法快速优雅的判断当前channel是属于哪个用户的;我看到别人绝大多数的实现是在创建一个channelId和用户标识的Map来管理

//key为channel的长id,channel.id().asLongText();value为用户id

private final ConcurrentHashMap channelAndUserMap = new ConcurrentHashMap<>();

其实这不是最合理的做法,正确的做法是利用Channel对象提供的AttributeMap来保存该通道的附带信息,很多人不知道Channel对象提供了一个绑定自定义数据的Map

使用示例

//用户id=>channel示例

private final ConcurrentHashMap channelMap = new ConcurrentHashMap<>();

/**

* 判断一个通道是否有用户在使用

* 可做信息转发时判断该通道是否合法

* @param channel

* @return

*/

public boolean hasUser(Channel channel) {

AttributeKey key = AttributeKey.valueOf("user");

return (channel.hasAttr(key) || channel.attr(key).get() != null);//netty移除了这个map的remove方法,这里的判断谨慎一点

}

/**

* 上线一个用户

*

* @param channel

* @param userId

*/

public void online(Channel channel, String userId) {

//先判断用户是否在web系统中登录?

//这部分代码个人实现,参考上面redis中的验证

this.channelMap.put(userId, channel);

AttributeKey key = AttributeKey.valueOf("user");

channel.attr(key).set(userId);

}

/**

* 根据用户id获取该用户的通道

*

* @param userId

* @return

*/

public Channel getChannelByUserId(String userId) {

return this.channelMap.get(userId);

}

/**

* 判断一个用户是否在线

*

* @param userId

* @return

*/

public Boolean online(String userId) {

return this.channelMap.containsKey(userId) && this.channelMap.get(userId) != null;

}

注意!!

很多人拿channel.id().asShortText()来记录标识channel,这是错误的!!!!!短id不保证全局唯一!!

9bc2112af0b1c6cbb356b56536ae9ca0.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值