我们在使用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心跳实现的更多相关文章

  1. Netty实现心跳机制

    netty心跳机制示例,使用Netty实现心跳机制,使用netty4,IdleStateHandler 实现.Netty心跳机制,netty心跳检测,netty,心跳 本文假设你已经了解了Netty的 ...

  2. Netty(一) SpringBoot 整合长连接心跳机制

    前言 Netty 是一个高性能的 NIO 网络框架,本文基于 SpringBoot 以常见的心跳机制来认识 Netty. 最终能达到的效果: 客户端每隔 N 秒检测是否需要发送心跳. 服务端也每隔 N ...

  3. Netty学习(八)-Netty的心跳机制

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a953713428/article/details/69378412我们知道在TCP长连接或者Web ...

  4. 9.7 dubbo心跳机制

    dubbo的心跳机制: 目的:检测provider与consumer之间的connection连接是不是还连接着,如果连接断了,需要作出相应的处理. 原理: provider:dubbo的心跳默认是在 ...

  5. Netty 心跳服务之 IdleStateHandler 源码分析

    前言:Netty 提供的心跳介绍 Netty 作为一个网络框架,提供了诸多功能,比如我们之前说的编解码,Netty 准备很多现成的编解码器,同时,Netty 还为我们准备了网络中,非常重要的一个服务- ...

  6. 判定生死的心跳机制 --ESFramework 4.0 快速上手(07)

    在Internet上采用TCP进行通信的系统,都会遇到一个令人头疼的问题,就是"掉线".而"TCP掉线"这个问题远比我们通常所能想象的要复杂的多 -- 网络拓扑 ...

  7. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  8. Java实现心跳机制

    一.心跳机制简介 在分布式系统中,分布在不同主机上的节点需要检测其他节点的状态,如服务器节点需要检测从节点是否失效.为了检测对方节点的有效性,每隔固定时间就发送一个固定信息给对方,对方回复一个固定信息 ...

  9. 心跳机制tcp keepalive的讨论、应用及“断网”、"断电"检测的C代码实现(Windows环境下)

    版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则会用法律维权. https://blog.csdn.net/stpeace/article/details/441 ...

随机推荐

  1. 我的Android进阶之旅------>解决如下错误failed to copy 'Settings2.apk' to '/system/app//Settings2.apk': Read-only

    push apk的时候报错 ouyangpeng@oyp-ubuntu:~/apk升级$ adb push Settings2.apk /system/app/ failed to copy 'Set ...

  2. SAP RFC 的介绍

    第一部分 RFC技术 什么是RFC? RFC是SAP系统和其他(SAP或非SAP)系统间的一个重要而常用的双向接口技术,也被视为SAP与外部通信的基本协议.简单地说,RFC过程就是系统调用当前系统外的 ...

  3. Allegro PCB中添加汉字

    注明出处:http://www.cnblogs.com/einstein-2014731/p/5650943.html Cadence用起来比AltiumDesigner要麻烦些,但是也更开放,更灵活 ...

  4. Java基础教程:面向对象编程[2]

    Java基础教程:面向对象编程[2] 内容大纲 访问修饰符 四种访问修饰符 Java中,可以使用访问控制符来保护对类.变量.方法和构造方法的访问.Java 支持 4 种不同的访问权限. default ...

  5. IOS AFNetWorking 设置超时时间

    (原创经验总结) 1.关于AF 超时的说法 系统默认的timeInterval  是60s  ASI默认是10s 但是有一个说法是 AF “AFN在GET条件下设置的NSURLRequest能起作用, ...

  6. 原生Base64编码/解码(OC与Swift)

    Objective-C NSString *plainString = @"foo"; Encoding NSData *plainData = [plainString data ...

  7. Android WiFi系统架构【转】

    本文转载自:http://blog.csdn.net/liuhaomatou/article/details/40398753 在了解WIFI模块的系统架构之前.我心中就有一个疑问,那么Android ...

  8. CodeForces - 540D Bad Luck Island —— 求概率

    题目链接:https://vjudge.net/contest/226823#problem/D The Bad Luck Island is inhabited by three kinds of ...

  9. 算法(Algorithms)第4版 练习 1.3.29

    代码实现: //1.3.29 package com.qiusongde.linkedlist; import java.util.Iterator; import java.util.NoSuchE ...

  10. Could not find JSON in http://updates.jenkins-ci.org/update-center.json?id=default&version=2.7.4

    14-Sep-2016 21:43:58.241 INFO [Download metadata thread] hudson.model.AsyncPeriodicWork$1.run Finish ...