聊聊心跳机制及netty心跳实现
我们在使用netty的时候会使用一个参数,ChannelOption.SO_KEEPALIVE为true, 设置好了之后再Linux系统才会对keepalive生效,但是linux里边需要配置几个参数,tcp_keepalive_time, tcp_keepalive_invl, tcp_keepalive_probes,如果不配置的时候都会是默认值。
tcp_keepalive_time 即给一个TCP连接发送心跳包最后的时间间隔某一段时间后继续发送心跳包,允许空闲的时间,然后再次发送心跳包,默认时间为7200秒,即2个小时发一次心跳包。
tcp_keepalive_invl,发送存活探测时候未收到对方回执的时候,需要间隔一段时间继续发送。默认为75秒。
tcp_keepalive_probes,如果发了存活探测的时候没有收到对方的回执,那么需要继续发送探测的次数,此时默认值为9次,也就是未收到回执的时候需要发送9次。
再理一次,间隔tcp_keepalive_time之后发送心跳探测,如果未收到对方回执的时候,需要间隔tcp_keepalive_invl设置的时间继续发送,一共需要发送tcp_keepalive_probes的次数。
这个是Linux系统的配置,如果要使用Linux的此功能需要设置SO_KEEPALIVE为true,同时设置其他几个参数。系统默认的SO_KEEPALIVE为false。因为这些情况的差异,所以netty提供了自己实现心跳的机制。
netty有心跳的实现方法 IdleStateHandler,其中有读空闲时间,写空闲时间,读写空闲时间,只要有一个满足条件会触发userEventTriggered方法。
public IdleStateHandler(
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds)
定义个消息内容吧,长度为Type的长度1 + 实际内容的长度5 = 6。Length为2个字节,Type为1个类型。
+----------+----------+----------------+
| Length |Type(byte)| Actual Content |
| 0x06 | 1 | "HELLO" |
+----------+----------+----------------+
定义公共的inbound方法,用于进行channelRead, sendPing, sendPong, userEventTriggered 方法。
package com.hqs.heartbeat.common; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent; import java.util.concurrent.atomic.AtomicInteger; /**
* @author huangqingshi
* @Date 2019-05-11
*/
public abstract class CustomeHeartbeatHandler extends SimpleChannelInboundHandler<ByteBuf> { public static final byte PING = 1;
public static final byte PONG = 2;
public static final byte CUSTOM_MSG = 3; protected String name;
private AtomicInteger heartbeatCount = new AtomicInteger(0); public CustomeHeartbeatHandler(String name) {
this.name = name;
} @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
if(byteBuf.getByte(2) == PING) {
sendPong(channelHandlerContext);
} else if(byteBuf.getByte(2) == PONG) {
System.out.println("get pong msg from " + channelHandlerContext
.channel().remoteAddress());
} else {
handleData(channelHandlerContext, byteBuf);
}
} protected abstract void handleData(ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf); @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channel read : " + msg);
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println(byteBuf.getByte(2));
super.channelRead(ctx, msg);
} protected void sendPong(ChannelHandlerContext channelHandlerContext) {
ByteBuf buf = channelHandlerContext.alloc().buffer(3);
buf.writeShort(3);
buf.writeByte(PONG);
channelHandlerContext.writeAndFlush(buf);
heartbeatCount.incrementAndGet();
System.out.println("send pong message to " + channelHandlerContext.channel().remoteAddress());
} protected void sendPing(ChannelHandlerContext channelHandlerContext) {
ByteBuf buf = channelHandlerContext.alloc().buffer(3);
buf.writeShort(3);
buf.writeByte(PING);
channelHandlerContext.writeAndFlush(buf);
heartbeatCount.incrementAndGet();
System.out.println("send ping message to " + channelHandlerContext.channel().remoteAddress());
} @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleStateEvent e = (IdleStateEvent) evt;
switch (e.state()) {
case ALL_IDLE:
handlALLIdle(ctx);
break;
case READER_IDLE:
handlReadIdle(ctx);
break;
case WRITER_IDLE:
handlWriteIdle(ctx);
break;
default:
break;
}
}
} protected void handlReadIdle(ChannelHandlerContext channelHandlerContext) {
System.out.println("READ_IDLE---");
} protected void handlWriteIdle(ChannelHandlerContext channelHandlerContext) {
System.out.println("WRITE_IDLE---");
} protected void handlALLIdle(ChannelHandlerContext channelHandlerContext) {
System.out.println("ALL_IDLE---");
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel:" + ctx.channel().remoteAddress() + " is active");
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel:" + ctx.channel().remoteAddress() + " is inactive");
}
}
定义Server的方法,设置读超时为10秒,采用固定长度方法进行内容分割:LengthFieldBasedFrameDecoder(1024, 0, 2, -2, 0),长度为1K 。一个主线程接收请求,四个线程处理请求。端口号设置为9999。
package com.hqs.heartbeat.server; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler; /**
* @author huangqingshi
* @Date 2019-05-11
*/
public class Server { public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(4); try {
ServerBootstrap bootstrapServer = new ServerBootstrap();
bootstrapServer.group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline();
channelPipeline.addLast(new IdleStateHandler(10, 0, 0));
channelPipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0,2, -2, 0));
channelPipeline.addLast(new ServerHandler());
}
});
Channel channel = bootstrapServer.bind(9999).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
Server的handler的处理方法:
package com.hqs.heartbeat.server; import com.hqs.heartbeat.common.CustomeHeartbeatHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; /**
* @author huangqingshi
* @Date 2019-05-11
*/
public class ServerHandler extends CustomeHeartbeatHandler { public ServerHandler() {
super("server");
} @Override
protected void handleData(ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) {
byte[] data = new byte[byteBuf.readableBytes() - 3];
ByteBuf responseBuf = Unpooled.copiedBuffer(byteBuf);
byteBuf.skipBytes(3);
byteBuf.readBytes(data);
String content = new String(data);
System.out.println(name + " get content : " + content);
channelHandlerContext.writeAndFlush(responseBuf);
} @Override
protected void handlReadIdle(ChannelHandlerContext channelHandlerContext) {
super.handlReadIdle(channelHandlerContext);
System.out.println(" client " + channelHandlerContext.channel().remoteAddress() + " reader timeout close it --");
channelHandlerContext.close();
}
}
定义Client类,所有超时时间为5秒,如果5秒没有读写的话则发送ping,如果失去连接之后inactive了就会重新连接,采用10秒出发一次。
package com.hqs.heartbeat.client; import com.hqs.heartbeat.common.CustomeHeartbeatHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler; import java.util.Random;
import java.util.concurrent.TimeUnit; /**
* @author huangqingshi
* @Date 2019-05-11
*/
public class Client { private NioEventLoopGroup workGroup = new NioEventLoopGroup(4);
private Channel channel;
private Bootstrap bootstrap; public static void main(String[] args) throws InterruptedException {
Client client = new Client();
client.start();
client.sendData();
} public void start() { try {
bootstrap = new Bootstrap(); bootstrap.group(workGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline channelPipeline = ch.pipeline()
.addLast(new IdleStateHandler(0,0,5))
.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, -2, 0))
.addLast(new ClientHandler(Client.this));
}
});
doConnect();
} catch (Exception e) {
throw new RuntimeException(e);
}
} public void sendData() throws InterruptedException {
Random random = new Random(System.currentTimeMillis());
for(int i = 0; i < 10000; i++) {
if(channel != null && channel.isActive()) {
String content = "client msg " + i;
ByteBuf byteBuf = channel.alloc().buffer(3 + content.getBytes().length);
byteBuf.writeShort(3 + content.getBytes().length);
byteBuf.writeByte(CustomeHeartbeatHandler.CUSTOM_MSG);
byteBuf.writeBytes(content.getBytes());
channel.writeAndFlush(byteBuf);
} Thread.sleep(random.nextInt(20000)); } } public void doConnect() {
if(channel != null && channel.isActive()) {
return;
} ChannelFuture future = bootstrap
.connect("127.0.0.1", 9999);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
channel = future.channel();
System.out.println("connect to server successfully");
} else {
System.out.println("Failed to connect to server, try after 10s"); future.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
doConnect();
}
}, 10, TimeUnit.SECONDS);
}
}
});
} }
定义clientHandler方法,读取时跳过长度+类型 2+1 三个字节,然后获取消息。连接断开之后则进行重连。
package com.hqs.heartbeat.client; import com.hqs.heartbeat.common.CustomeHeartbeatHandler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; /**
* @author huangqingshi
* @Date 2019-05-11
*/
public class ClientHandler extends CustomeHeartbeatHandler { private Client client; public ClientHandler(Client client) {
super("client");
this.client = client;
} @Override
protected void handleData(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) {
byte[] data = new byte[byteBuf.readableBytes() - 3];
byteBuf.skipBytes(3);
byteBuf.readBytes(data);
String content = new String(data);
System.out.println(name + " get content:" + content);
} @Override
protected void handlALLIdle(ChannelHandlerContext channelHandlerContext) {
super.handlALLIdle(channelHandlerContext);
sendPing(channelHandlerContext);
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
client.doConnect();
}
}
好了,总体的netty心跳实现机制就这么多,希望能帮助到大家。
github地址:https://github.com/stonehqs/heartbeat
聊聊心跳机制及netty心跳实现的更多相关文章
- Netty实现心跳机制
netty心跳机制示例,使用Netty实现心跳机制,使用netty4,IdleStateHandler 实现.Netty心跳机制,netty心跳检测,netty,心跳 本文假设你已经了解了Netty的 ...
- Netty(一) SpringBoot 整合长连接心跳机制
前言 Netty 是一个高性能的 NIO 网络框架,本文基于 SpringBoot 以常见的心跳机制来认识 Netty. 最终能达到的效果: 客户端每隔 N 秒检测是否需要发送心跳. 服务端也每隔 N ...
- Netty学习(八)-Netty的心跳机制
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a953713428/article/details/69378412我们知道在TCP长连接或者Web ...
- 9.7 dubbo心跳机制
dubbo的心跳机制: 目的:检测provider与consumer之间的connection连接是不是还连接着,如果连接断了,需要作出相应的处理. 原理: provider:dubbo的心跳默认是在 ...
- Netty 心跳服务之 IdleStateHandler 源码分析
前言:Netty 提供的心跳介绍 Netty 作为一个网络框架,提供了诸多功能,比如我们之前说的编解码,Netty 准备很多现成的编解码器,同时,Netty 还为我们准备了网络中,非常重要的一个服务- ...
- 判定生死的心跳机制 --ESFramework 4.0 快速上手(07)
在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是"掉线".而"TCP掉线"这个问题远比我们通常所能想象的要复杂的多 -- 网络拓扑 ...
- Spark RPC框架源码分析(三)Spark心跳机制分析
一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...
- Java实现心跳机制
一.心跳机制简介 在分布式系统中,分布在不同主机上的节点需要检测其他节点的状态,如服务器节点需要检测从节点是否失效.为了检测对方节点的有效性,每隔固定时间就发送一个固定信息给对方,对方回复一个固定信息 ...
- 心跳机制tcp keepalive的讨论、应用及“断网”、"断电"检测的C代码实现(Windows环境下)
版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则会用法律维权. https://blog.csdn.net/stpeace/article/details/441 ...
随机推荐
- 性能测试--yslow
YSlow YSlow可以对网站的页面进行分析,并告诉你为了提高网站性能,如何基于某些规则而进行优化. YSlow可以分析任何网站,并为每一个规则产生一个整体报告,如果页面可以进行优化,则YSlow会 ...
- Hadoop实战-Flume之自定义Source(十八)
import java.nio.charset.Charset; import java.util.HashMap; import java.util.Random; import org.apach ...
- RDS for MySQL 删除数据后空间没有减少处理方法
公司的程序和数据库部署在阿里云上,数据库使用的是阿里云的RDS,这天,经理在开发群中发了一个信息: 您的RDS实例rm********0oq的磁盘在过去一周平均使用率已超过80.%,建议您对实例规格进 ...
- Hadoop集群部署-Hadoop 运行集群后Live Nodes显示0
可以尝试以下步骤解决: 1 ,分别删除:主节点从节点的 /usr/local/hadoop-2.6.2/etc/tmp 下得所有文件; 2: 编辑cd usr/local/hadoop-2.6. ...
- 【AWS】亚马逊云常用服务解释
新公司使用的是亚马逊服务,刚开始的时候,对很多名词不太明白,总结了一下如下 1,EC2 这个是亚马逊的一种服务器服务,可以理解为跟vmware差不多,EC2为虚拟机提供载体,EC2上跑虚拟机服务器. ...
- MySQL修改配置 区分大小写
在使用mysql的时候,数据库名,表名,字段名等有大小写的区分,这个可以通过配置文件设置.如果设置了严格区分大小写,在访问表的时候没有注意到表名的大小写,将会报出表不存在的错误.下面是修改配置文件: ...
- 基于ajax的登录
验证码 当登录一个网站的时候往往会有验证码. python生成随机验证码,需要使用到 PIL 模块 安装 : pip3 install pillow 1. 创建图片 我们现在写的验证码属 ...
- [Android] Gradle 安装
Gradle安装非常简单,只要从官网下载压缩包,解压,修改一下环境变量即可. 笔者写本篇随笔时,版本是1.12. Windows下安装 1 到官网(http://www.gradle.org/down ...
- java.lang.UnsupportedClassVersionError: com/dw/Function : Unsupported major.minor version 52.0
本地环境中有jdk7和jdk8,当把jdk8改为jdk7时,出现如下错误,是jdk版本的问题,在Properties-->JAVA Compiler-中的Compiler compliance ...
- 安装wampserver 计算机丢失msvcr100.dll
刚刚重新安装了Windows 7 64位系统,再安装Wampserver 2时却提示系统错误,如下图所示: 在网上下载了MSVCR100.dll放到system32文件夹下依然没有用. 百度搜索了一下 ...