netty5心跳与业务消息分发实例
继续基于我们之前的例子(参见netty5自定义私有协议实例),这次我们加上连接校验和心跳机制:
只要校验通过,客户端发送心跳和业务消息是两个不同的事件发送的,彼此互不干扰。针对以上流程,我们需要增加4个handler:客户端请求handler、心跳handler ,服务端校验handler、心跳处理handler。当然,引导类也得添加上面对应的handler。上代码:
新增客户端首次连接handler:
package com.wlf.netty.nettyclient.handler; import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j; @Slf4j
public class ControlClientHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buildControlReq());
} /**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[Client] conrol request error: {}", cause.getMessage());
ctx.fireExceptionCaught(cause);
} /**
* 构造请求消息体
*
* @return
*/
private NettyMessage buildControlReq() {
NettyMessage nettyMessage = new NettyMessage();
Header header = new Header();
byte[] data = buildControlData();
header.setDelimiter(0xABEF0101);
header.setLength(data.length);
header.setType((byte) 0);
header.setReserved((byte) 0);
nettyMessage.setHeader(header); // 设置数据包
nettyMessage.setData(data);
return nettyMessage;
} /**
* 构造控制请求消息体
*
* @return
*/
private byte[] buildControlData() {
byte[] result = new byte[2]; result[0] = (byte) 1; result[1] = (byte) 16;
return result;
}
}
服务端校验handler:
package com.wlf.netty.nettyserver.handler; import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j; import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; @Slf4j
public class ControlServerHandler extends ChannelHandlerAdapter { // 白名单列表
private String[] whiteList = new String[]{"127.0.0.1"}; private Map<String, Boolean> nodeCheck = new ConcurrentHashMap<>(); @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage nettyMessage = (NettyMessage) msg; // 如果是控制数据格式请求消息,说明是客户端首次请求,校验白名单,否则进入下一个处理流程
if (nettyMessage.getHeader() != null &&
nettyMessage.getHeader().getType() == (byte) 0) {
String nodeIndex = ctx.channel().remoteAddress().toString();
NettyMessage controlResponse = null; if (nodeCheck.containsKey(nodeIndex)) {
log.warn("request ip : {} has requested.", nodeIndex);
controlResponse = buildResponse(false);
} else {
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
String ip = address.getAddress().getHostAddress();
boolean isOK = false; for (String whiteIp : whiteList) {
if (ip.equals(whiteIp)) {
isOK = true;
break;
}
} if (isOK) {
nodeCheck.put(nodeIndex, true);
// 白名单校验通过,校验是否支持PCM格式
byte[] data = nettyMessage.getData();
ByteBuf buf = Unpooled.buffer(2);
buf.writeBytes(data);
byte sample = buf.readByte(); if (sample != (byte) 1) {
log.error("sample : {} is not 1", sample);
controlResponse = buildResponse(false);
} else {
controlResponse = buildResponse(true);
}
} else {
log.error("ip : {} is not in whiteList : {}.", ip, whiteList);
controlResponse = buildResponse(false);
} }
log.info("[server] The control response is : {}, data : {}", controlResponse, controlResponse.getData());
ctx.writeAndFlush(controlResponse);
} else {
ctx.fireChannelRead(msg);
}
} /**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[server] control response error: {}", cause.getMessage());
// 删除缓存
nodeCheck.remove(ctx.channel().remoteAddress().toString());
ctx.fireExceptionCaught(cause);
} /**
* 构造响应消息体
*
* @param isOk
* @return
*/
private NettyMessage buildResponse(boolean isOk) {
NettyMessage nettyMessage = new NettyMessage();
Header header = new Header();
byte[] data = buildData(isOk);
header.setDelimiter(0xABEF0101);
header.setLength(data.length);
header.setType((byte) 0);
header.setReserved((byte) 0);
nettyMessage.setHeader(header);
nettyMessage.setData(data);
return nettyMessage;
} /**
* 构建控制数据格式响应消息体
*
* @param isOk
* @return
*/
private byte[] buildData(boolean isOk) {
ByteBuf result = null;
if (isOk) {
result = Unpooled.buffer(8);
// 生成sid
result.writeInt(buildSid()); // 心跳发送间隔,5000毫秒秒
result.writeInt(5000);
} else {
result = Unpooled.buffer(1);
result.writeByte((byte) -1);
} return result.array();
} private int buildSid() {
int max = 100, min = 1;
long randomNum = System.currentTimeMillis();
return (int) (randomNum % (max - min) + min);
}
}
心跳客户端handler:
package com.wlf.netty.nettyclient.handler; import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.ScheduledFuture;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; @Slf4j
public class HeartBeatClientHandler extends ChannelHandlerAdapter {
private volatile int interval = 5000;
private volatile ScheduledFuture<?> heartBeat; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage nettyMessage = (NettyMessage) msg; // 接收控制数据响应消息成功,发送心跳给服务端
if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 0) {
byte[] data = nettyMessage.getData();
ByteBuf buf = Unpooled.buffer(8);
buf.writeBytes(data);
int sid = buf.readInt();
interval = buf.readInt();
log.info("[client] control response is OK, header : {}. sid : {}, interval : {}", nettyMessage.getHeader(), sid, interval); // 每interval(默认5000)豪秒发送一次心跳请求到服务端
heartBeat = ctx.executor().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NettyMessage heartBeat = buildHeartBeat(sid);
log.info("[client] Client send heart beat message to server : ----> {}", heartBeat);
ctx.writeAndFlush(heartBeat);
}
},
0, interval, TimeUnit.MILLISECONDS); // 消息继续向后传
ctx.fireChannelRead(msg);
} else {
ctx.fireChannedRead(msg);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[Client] heart request error: {}", cause.getMessage());
if (heartBeat != null) {
heartBeat.cancel(true);
heartBeat = null;
}
ctx.fireExceptionCaught(cause);
} /**
* 构造心跳请求消息体
*
* @return
*/
private NettyMessage buildHeartBeat(int sid) {
NettyMessage message = new NettyMessage();
Header header = new Header();
byte[] data = buildData(sid);
header.setDelimiter(0xABEF0101);
header.setLength(data.length);
header.setType((byte) 3);
header.setReserved((byte) 0);
message.setHeader(header); // 设置数据包
message.setData(data);
return message;
} /**
* 构建心跳响应消息体
*
* @param sid
* @return
*/
private byte[] buildData(int sid) {
ByteBuf result = Unpooled.buffer(4);
result.writeInt(sid);
return result.array();
} }
服务端心跳handler:
package com.wlf.netty.nettyserver.handler; import com.wlf.netty.nettyapi.constant.MessageType;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j; @Slf4j
public class HeartBeatServerHandler extends ChannelHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage nettyMessage = (NettyMessage) msg; // 接收到心跳请求,打印心跳消息,否则进入下一处理流程
if (nettyMessage.getHeader() != null &&
nettyMessage.getHeader().getType() == (byte) 3) {
log.info("[server] Receive client heart beat message : ----> {}", nettyMessage);
} else {
ctx.fireChannelRead(msg);
}
} /**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[server] heart response error: {}", cause.getMessage()); ctx.fireExceptionCaught(cause);
} }
客户端发送业务消息NettyClientHandler修改,发送触发从channelAcitve事件改为channelRead事件:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage nettyMessage = (NettyMessage) msg; // 接收控制数据响应消息成功,每5秒发送pcm数据
if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 0) {
ctx.writeAndFlush(buildClientRequest());
} // @Override
// public void channelActive(ChannelHandlerContext ctx) throws Exception {
// ctx.writeAndFlush(buildClientRequest());
// }
客户端引导类NettyClient修改,新增handler:
public void connect(int port, String host) throws Exception {
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new NettyMessageDecoder());
channel.pipeline().addLast(new NettyMessageEncoder());
channel.pipeline().addLast(new ControlClientHandler());
channel.pipeline().addLast(new HeartBeatClientHandler());
channel.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
workGroup.shutdownGracefully();
}
}
服务端引导类修改:
public void bind(int port) throws Exception {
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new NettyMessageDecoder());
channel.pipeline().addLast(new NettyMessageEncoder());
channel.pipeline().addLast(new ControlServerHandler());
channel.pipeline().addLast(new HeartBeatServerHandler());
channel.pipeline().addLast(new NettyServerHandler());
}
});
// 绑定端口
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
我们跑起来看看,先跑服务端再跑客户端:
服务端输出:
22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.ControlServerHandler - [server] The control response is : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=0, reserved=0}, data=[B@65aac359}, data : [0, 0, 0, 18, 0, 0, 19, -120]
22:55:33.741 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - [server] server receive client message : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=1, reserved=0}, data=[B@75f76fa3}
22:55:33.752 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - data length: 8
22:55:33.752 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - startTime: 1572105096532
22:55:38.747 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@77e50de}
22:55:43.746 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@75305725}
22:55:48.769 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@29515fc1}
22:55:53.754 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1dfc1da8}
22:55:58.739 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@68f15669}
22:56:03.738 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1f8fad2d}
22:56:08.742 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.HeartBeatServerHandler - [server] Receive client heart beat message : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@3c83464a}
客户端输出:
22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] control response is OK, header : Header{delimiter=-1410399999, length=8, type=0, reserved=0}. sid : 18, interval : 5000
22:55:33.725 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@b1db7b2}
22:55:38.747 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@4958706c}
22:55:43.746 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1fb0fa50}
22:55:48.769 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@4aeea171}
22:55:53.754 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@2c282fb9}
22:55:58.739 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@2679e140}
22:56:03.738 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@3e03eda5}
22:56:08.742 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyclient.handler.HeartBeatClientHandler - [client] Client send heart beat message to server : ----> NettyMessage{header=Header{delimiter=-1410399999, length=4, type=3, reserved=0}, data=[B@1d9363d}
从输出可以看到心跳是正常的,业务消息也发送了,但有一点要注意,就是业务消息只有一条,而且在心跳之前发送给了客户端。如果业务消息也是阻塞性的,那么就会出现问题,详见netty5心跳与阻塞性业务消息分发实例。
netty5心跳与业务消息分发实例的更多相关文章
- netty5心跳与阻塞性业务消息分发实例
继续之前的例子(netty5心跳与业务消息分发实例),我们在NettyClientHandler把业务消息改为阻塞性的: package com.wlf.netty.nettyclient.handl ...
- .net 分布式架构之业务消息队列
开源QQ群: .net 开源基础服务 238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BusinessMQ ## 业务消息队列 ##业务消 ...
- 使用Netty4实现基本的消息分发
示例工程代码 可从附件下载 具体的说明和用法在后面介绍 需求与目的 一个游戏服务端需要处理各种业务逻辑,每一种业务逻辑都对应着一个请求消息和一个响应消息.那么服务端需要把这些不同的消息自动分发到对应的 ...
- cocos creator主程入门教程(六)—— 消息分发
五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以TypeScript为介绍语言. 本篇开始介绍游戏业务架构相关的内容.在游戏业务层,所有需要隔离的系统和模块间通信都可以通过消息分发解耦. ...
- 深入详解美团点评CAT跨语言服务监控(四)服务端消息分发
这边首先介绍下大众点评CAT消息分发大概的架构如下: 图4 消息分发架构图 分析管理器的初始化 我们在第一章讲到服务器将接收到的消息交给解码器(MessageDecoder)去做解码最后交给具体的消费 ...
- 一个可以代替冗长switch-case的消息分发小框架
在项目中,我需要维护一个应用层的字节流协议.这个协议的每条报文都是一个字节数组,数组的头两个字节表示消息的传送方向,第三.四个字节表示消息ID,也就是消息种类,再往后是消息内容.时间戳.校验码等……整 ...
- Android 消息分发机制
Android 中针对耗时的操作,放在主线程操作,轻者会造成 UI 卡顿,重则会直接无响应,造成 Force Close.同时在 Android 3.0 以后,禁止在主线程进行网络请求. 针对耗时或者 ...
- delphi VCL研究之消息分发机制-delphi高手突破读书笔记
1.VCL 概貌 先看一下VCL类图的主要分支,如图4.1所示.在图中可以看到,TObject是VCL的祖先类,这也是Object Pascal语言所规定的.但实际上,TObject以及TObject ...
- 深入解析Windows窗体创建和消息分发
Windows GUI採用基于事件驱动的编程模型,其实差点儿全部的界面库都是这样做的.在纯粹的Window32 SDK编程时代.人们还能够搞懂整个Windows窗口创建和消息的流通过程.可是在如今各种 ...
随机推荐
- dns-prefetch应用好,网上速度能提高一半!
今天一个朋友给我说在网页上添加dns-prefetch,网页访问速度能提高,于是我百度查询关于dns-prefetch. DNS Prefetch,即DNS预获取,是前端优化的一部分.一般来说,在前端 ...
- vetur 和 npm run lint 格式化不一致
vetur 的 template(html) 默认使用的格式化插件是 prettyhtml,虽然可以选 prettier,和 npm run lint 的格式化保持一致,但是有时候会影响到 scrip ...
- 大小端示例-arm c51
大小端系列文章https://blog.csdn.net/liming0931/article/details/100016425 MDK(Keil5,STM32F407)C语言: #include ...
- mybatis oracle两种方式批量插入数据
mybatis oracle两种方式批量插入数据 注意insert,一定要添加: useGeneratedKeys="false" ,否者会报错. <insert id=&q ...
- 学到了林海峰,武沛齐讲的Day35 完 协程
day3 requests.get 爬网页 greenlet 协程模块 还有asy!!!模快(后续版本) day4 事件驱动 day5 基础学习 day6 基础学习 da ...
- winform DateTimePicker 设置成秒
C# Windows窗体应用中,用到时间选择控件DateTimePicker,发现不能选择时分秒,难道要自己写一个控件?! 答案是否定的,通过属性修改是可以选择时间的,DateTimePicker完全 ...
- learning java 字符串常用操作
// 字符串索引取值 "; System.)); // 字符串比较 "; "; "; System.out.println(s1.compareTo(s2)); ...
- EL获取域中的数据
EL(Expression Language)是表达式语言,EL的使用可以减少JAVA代码的书写. 1.EL表达式中的常量: <body bgcolor="#7fffd4"& ...
- Ceph osd故障恢复
1 调高osd的日志等级 加上红框那一行就可以了 osd的日志路径:/var/log/ceph/ceph-osd.3.log 注意:加上了这一行后日志会刷很多,所以要特别注意日志容量的变化,以防把v ...
- Halting Problem
Halting Problem: 传送门:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4049 总结一个小规律:题目中给的 ...