作者:南城之南
出处:https://www.cnblogs.com/liang…
前言
思考一个性能业务, 在 web
程序中向指定的某个用户进行实时通信
在 Web 使用的 Socket
通信性能中 (如在线客服), 为保障点对点通信. 而这个看似简略的依据用户寻到起channel
通道理论会碰到不少问题
- web 程序中的
Http
协定是无状态的 - 个别我的项目中
socket
服务和web
我的项目是独立部署的 socket
连贯存在重连的状况, 而Channel
对象每次都不一样Channel
是面向网卡绑定的, 无奈序列化
解决方案
通过治理一个线程平安的 用户标识
(如用户主键) 和对应 channel
的map
链表
private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
那么问题来了,
- 在
netty
模块中怎么失去这个用户标识
? - 又如何保障
netty socket
模块能够平安的辨认某个通道属于某个用户?(这个能够像下面一样的形式解决) netty socket
模块接管到一条音讯又任何证实这条通道是可信的?
在 netty
的实现中是没有认证也没有 HttpSession
这个货色的, 也就是说. 在 netty
程序线程中是无奈失去 web 我的项目登录的用户状况的.
出于这点, 参考 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<String, Channel>
, 是无奈疾速优雅的判断以后channel
是属于哪个用户的; 我看到他人绝大多数的实现是在创立一个 channelId
和用户标识
的 Map 来治理
//key 为 channel 的长 id,channel.id().asLongText();value 为用户 id
private final ConcurrentHashMap<String, String> channelAndUserMap = new ConcurrentHashMap<>();
其实这不是最正当的做法, 正确的做法是利用 Channel
对象提供的 AttributeMap
来保留该通道的附带信息, 很多人不晓得 Channel
对象提供了一个绑定自定义数据的 Map
应用示例:
// 用户 id=>channel 示例
private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
/**
* 判断一个通道是否有用户在应用
* 可做信息转发时判断该通道是否非法
* @param channel
* @return
*/
public boolean hasUser(Channel channel) {AttributeKey<String> 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<String> 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 不保障全局惟一!!
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!