点击关注公众号,Java干货及时送达![](https://img6.php1.cn/3cdc5/a0d1/cd5/0010cf60078ae586.jpeg)
作者:南城之南
出处:www.cnblogs.com/liangshu/p/12459657.html
前言
考虑一个功能业务,在web
程序中向指定的某个用户进行实时通讯
在Web运用的Socket
通讯功能中(如在线客服),为保证点对点通讯.而这个看似简单的根据用户寻到起channel
通道实际会碰到不少问题
web程序中的Http
协议是无状态的
一般项目中socket
服务和web
项目是独立部署的
socket
连接存在重连的情况,而Channel
对象每次都不一样
Channel
是面向网卡绑定的,无法序列化
解决方案
通过管理一个线程安全的用户标识
(如用户主键)和对应channel
的map
链表
private final ConcurrentHashMap channelMap &#61; new ConcurrentHashMap<>();
那么问题来了,
在nett
的实现中是没有认证也没有HttpSession
这个东西的,也就是说.在netty
程序线程中是无法得到web项目登录的用户情况的。另外&#xff0c;Netty 系列面试题和答案全部整理好了&#xff0c;微信搜索Java技术栈&#xff0c;在后台发送&#xff1a;面试&#xff0c;可以在线阅读。
出于这点,参考web
项目集群的session
共享方案.可以在Redis等缓存中保存登录信息.
在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
是属于哪个用户的&#xff0c;我看到别人绝大多数的实现是在创建一个channelId
和用户标识
的Map来管理。
//key为channel的长id,channel.id().asLongText();value为用户id
private final ConcurrentHashMap channelAndUserMap &#61; new ConcurrentHashMap<>();
其实这不是最合理的做法,正确的做法是利用Channel
对象提供的AttributeMap
来保存该通道的附带信息,很多人不知道Channel
对象提供了一个绑定自定义数据的Map。
使用示例&#xff1a;
//用户id&#61;>channel示例
private final ConcurrentHashMap channelMap &#61; new ConcurrentHashMap<>();
/**
* 判断一个通道是否有用户在使用
* 可做信息转发时判断该通道是否合法
* &#64;param channel
* &#64;return
*/
public boolean hasUser(Channel channel) {
AttributeKey key &#61; AttributeKey.valueOf("user");
return (channel.hasAttr(key) || channel.attr(key).get() !&#61; null);//netty移除了这个map的remove方法,这里的判断谨慎一点
}
/**
* 上线一个用户
*
* &#64;param channel
* &#64;param userId
*/
public void online(Channel channel, String userId) {
//先判断用户是否在web系统中登录?
//这部分代码个人实现,参考上面redis中的验证
this.channelMap.put(userId, channel);
AttributeKey key &#61; AttributeKey.valueOf("user");
channel.attr(key).set(userId);
}
/**
* 根据用户id获取该用户的通道
*
* &#64;param userId
* &#64;return
*/
public Channel getChannelByUserId(String userId) {
return this.channelMap.get(userId);
}
/**
* 判断一个用户是否在线
*
* &#64;param userId
* &#64;return
*/
public Boolean online(String userId) {
return this.channelMap.containsKey(userId) && this.channelMap.get(userId) !&#61; null;
}
注意!!
很多人拿channel.id().asShortText()
来记录标识channel
,这是错误的!!!!!短id不保证全局唯一!!
另外&#xff0c;关注公众号Java技术栈&#xff0c;在后台回复&#xff1a;面试&#xff0c;可以获取我整理的 Java 系列面试题和答案&#xff0c;非常齐全。
![](https://img6.php1.cn/3cdc5/a0d1/cd5/e678aa20013dff28.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/fc3eb30cb023020b.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/cf95e07be33f3708.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/87d91bce898b4dfd.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/86b575f4e605dba2.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/96f308d01574ed4a.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/aebd4cb7d4e5c3c6.jpeg)
关注Java技术栈看更多干货
![](https://img6.php1.cn/3cdc5/a0d1/cd5/786f59969f375fec.jpeg)
![](https://img6.php1.cn/3cdc5/a0d1/cd5/275b363e8d59c0bb.jpeg)
获取 Spring Boot 实战笔记&#xff01;