Netty实现长连接的原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty实现长连接的原理相关的知识,希望对你有一定的参考价值。
参考技术A 主要逻辑 :使用netty实现长连接,主要靠心跳来维持服务器端及客户端连接。
主要的实现逻辑如下:
服务器端 :(HeartBeatRespHandler)
1, 服务器在网络空闲操作一定时间后,服务端失败心跳计数器加1。
2, 如果收到客户端的ping心跳包,则清零失败心跳计数器,如果连续n次未收到客户端的ping心跳包,则关闭链路,释放资源,等待客户端重连。
客户端 :(HeartBeatReqHandler)
1, 客户端网络空闲在一定时间内没有进行写操作时,则发送一个ping心跳包。
2, 如果服务器端未在发送下一个心跳包之前回复pong心跳应答包,则失败心跳计数器加1。
3, 如果客户端连续发送n(此处根据具体业务进行定义)次ping心跳包,服务器端均未回复pong心跳应答包,则客户端断开连接,间隔一定时间进行重连操作,直至连接服务器成功。
API调用Netty长链接执行发送消息(在线数用户列表)
前言
在原项目中,对于WebSocket的长连接,聊天系统并没有开放接口出来给第三方的系统调用,只有我们系统内部的人员才知道,确切的说系统内部也没有实际的查询接口,那么我们今天就来实现这个功能。
在Netty下的Websocket长连接中,以API形式获取在线用户数,与在线用户列表,并针对某个用户已API调用的形式进行数据发送,而不需要所谓的前端页面去创建websocket连接。
实践流程
存放Channel的容器
首先,我们需要一个类似ChannelGroup的连接池来存放我们的连接实例,这里我直接在原来本地模拟的一个LikeRedisTemplate中新建了一个ConcurrentHashMap,用于存放对应的用户名——连接实例的键值对。
方便后期API调用时可以通过这个LikeRedisTemplate中的这个Map进行获取、删除及相关信息。
/**存放链接池实例*/private Map<Object,Object> ChannelRedisMap = new ConcurrentHashMap<>();/** * 存储对应的用户名与Netty链接实例 * @param name 登录用户名 * @param channel Netty链接实例 */public void saveChannel(Object name,Object channel){
ChannelRedisMap.put(name,channel);
}/** * 获取存储池中的链接实例 * @param name 登录用户名 * @return {@link io.netty.channel.Channel 链接实例} */public Object getChannel(Object name){ return ChannelRedisMap.get(name);
}/** * 删除存储池实例 * @param name 登录用户名 */public void deleteChannel(Object name){
ChannelRedisMap.remove(name);
} /** * 获取储存池链接数 * @return 在线数 */public Integer getSize(){ return ChannelRedisMap.size();
}/** * 返回在线用户列表信息 * @return 用户名列表 */public Object getOnline() {
List<Object> result = new ArrayList<>(); for (Object key:ChannelRedisMap.keySet()){
result.add(key);
} return result;
}
Handler中执行存储操作
有了容器,我们就需要在对应的位置进行连接实例的键值对存储,我目前选择了在聊天消息传输过程中进行存储,暂时还没有抽象出来。
并在连接断开时也要做相关的处理。
//用户登录判断if (redisTemplate.check(incoming.id(),rName)){ //临时存储聊天数据
cacheTemplate.save(rName,rMsg); //存储随机链接ID与对应登录用户名
redisTemplate.save(incoming.id(),rName); //存储登录用户名与链接实例,方便API调用链接实例
redisTemplate.saveChannel(rName,incoming);
}else{
incoming.writeAndFlush(new TextWebSocketFrame("存在二次登陆,系统已为你自动断开本次链接"));
channels.remove(ctx.channel());
ctx.close(); return;
}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception { //删除存储池对应实例
String name = (String) redisTemplate.getName(ctx.channel().id());
redisTemplate.deleteChannel(name); //删除默认存储对应关系
redisTemplate.delete(ctx.channel().id());
channels.remove(ctx.channel());
}
发送方法
我直接在SendUtil中写一个系统发送的方法,输出也是转为TextWebSocketFrame
/** * 想指定链接发送数据 * @param msg 消息 * @param channel 指定链接 * @return {@link String} */public static String sendTest(String msg,Channel channel) { try {
channel.writeAndFlush(new TextWebSocketFrame( "[系统API]" + msg)); return "success";
}catch (Exception e){
e.printStackTrace(); return "error";
}
}
定义API
这个就简单一些了,定义一个统一返回的Bean,还有API的返回工具类,然后写对应的API接口方法。
@RestController@RequestMapping("/back")public class NCBackController { @Autowired
private LikeRedisTemplate redisTemplate; /** * 获取在线用户数 * @return {@link ResultVo} */
@GetMapping("/size") public ResultVo getSize(){ return ResultVOUtil.success(redisTemplate.getSize());
} /** * 获取在线用户列表 * @return {@link ResultVo} */
@GetMapping("/online") public ResultVo getOnline(){ return ResultVOUtil.success(redisTemplate.getOnline());
} /** * API调用向在线用户发送消息 * @param name 用户名 * @param msg 消息 * @return {@link ResultVo} */
@PostMapping("/send") public ResultVo send(@RequestParam String name,@RequestParam String msg){
Channel channel = (Channel) redisTemplate.getChannel(name); if (channel == null){ return ResultVOUtil.error(555,"当前用户连接已断开");
}
String result = SendUtil.sendTest(msg,channel); return ResultVOUtil.success(result);
}
}
效果
我在项目中添加Swagger方便查看与简单测试API,引入对应pom,在启动类加一个注解即可。
启动项目后登陆界面,发送了一个基本消息。
Swagger这边的页面打开后,测试几个API,都是成功的。
好了,结尾还是成功的,不过作为一个好产品是不能仅仅这样的,后续我们继续完善。
本项目是本人近期GitHub的核心发展项目,有兴趣的朋友可以去了解下
GitHub
以上是关于Netty实现长连接的原理的主要内容,如果未能解决你的问题,请参考以下文章