一、概念介绍
网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现。但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题。可是如何判断这个套接字是否还可以使用呢?这个就需要在系统中创建心跳机制。其实TCP中已经为我们实现了一个叫做心跳的机制。如果你设置了心跳,那TCP就会在一定的时间(比如你设置的是3秒钟)内发送你设置的次数的心跳(比如说2次),并且此信息不会影响你自己定义的协议。所谓“心跳”就是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着。 以确保链接的有效性。

所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。比如有些通信软件长时间不使用,要想知道它的状态是在线还是离线就需要心跳包,定时发包收包。发包方:可以是客户也可以是服务端,看哪边实现方便合理。一般是客户端。服务器也可以定时轮询发心跳下去。心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项。系统默认是设置的是2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。心跳包一般来说都是在逻辑层发送空的包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。只需要send或者recv一下,如果结果为零,则为掉线。

但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀当然,这个自然是要由逻辑层根据需求去做了。总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

二、心跳实现
使用TCP协议层的Keeplive机制,但是该机制默认的心跳时间是2小时,依赖操作系统实现不够灵活;

心跳机制一般来说都是在逻辑层发送空的包来实现的,比如Netty的IdleStateHandler类实现心跳机制。

心跳机制实现逻辑:每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息给客户端,如果服务端几分钟内没有收到客户端信息则视客户端断开。

在Netty中IdleStateHandler主要用来检测远端是否存活,如果不存活或活跃则对空闲Socket连接进行处理避免资源的浪费;IdleStateHandler实现对三种心跳的检测,分别是readerIdleTime、writerIdleTime和allIdleTime,参数解释如下:
1)readerIdleTime:读超时时间
2)writerIdleTime:写超时时间
3)allIdleTime:所有类型的超时时间

所以在channelPipeline中加入IdleStateHandler,我们在handler中提示的是5秒读,所以我们服务端的配置的是:

ph.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));

因为服务端必须5秒接受一次心跳请求,那么客户端的配置:

ph.addLast( new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS));

userEventTriggered是Netty 处理心跳超时事件,在IdleStateHandler设置超时时间,如果达到了,就会直接调用该方法。如果没有超时则不调用。我们重写该方法的话,就可以自行进行相关的业务逻辑处理了。

三、IdleStateHandler心跳检测实例
a、服务端
HeartNettyServer——服务端启动类

package com.dxfx.netty.demo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* 服务端启动类
*
* @author Administrator
*
*/
public class HeartNettyServer {
public static void main(String[] args) throws InterruptedException {
// 首先,netty通过ServerBootstrap启动服务端
ServerBootstrap server = new ServerBootstrap();
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup =new NioEventLoopGroup();
//第1步定义两个线程组,用来处理客户端通道的accept和读写事件
//parentGroup用来处理accept事件,childgroup用来处理通道的读写事件
//parentGroup获取客户端连接,连接接收到之后再将连接转发给childgroup去处理
server.group(parentGroup, childGroup); //用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
//用来初始化服务端可连接队列
//服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
server.option(ChannelOption.SO_BACKLOG, 128); //第2步绑定服务端通道
server.channel(NioServerSocketChannel.class); //第3步绑定handler,处理读写事件,ChannelInitializer是给通道初始化
server.childHandler(new HeartNettyServerFilter()); //第4步绑定8080端口
ChannelFuture future = server.bind(8080).sync();
//当通道关闭了,就继续往下走
future.channel().closeFuture().sync();
} }

HeartNettyServerFilter——服务端过滤器,如编解码和心跳的设置

package com.dxfx.netty.demo;

import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler; /**
* 服务端过滤器,如编解码和心跳的设置
*
* @author Administrator
*
*/
public class HeartNettyServerFilter extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline cp = sc.pipeline();
cp.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS)); // 解码和编码,应和客户端一致
cp.addLast(new StringDecoder());
cp.addLast(new StringEncoder()); //处理服务端的业务逻辑
cp.addLast(new HeartNettyServerHandler());
}
}

HeartNettyServerHandler——处理服务端业务逻辑:心跳超时处理、客服端返回的数据处理

package com.dxfx.netty.demo;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent; /**
* 处理服务端业务逻辑:心跳超时处理、客服端返回的数据处理
*
* @author Administrator
*
*/
public class HeartNettyServerHandler extends ChannelInboundHandlerAdapter {
/** 空闲次数 */
private int idle_count = 1;
/** 发送次数 */
private int count = 1; /**
* 超时处理,如果5秒没有收到客户端的心跳,就触发; 如果超过两次,则直接关闭;
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
if (obj instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) obj;
if (IdleState.READER_IDLE.equals(event.state())) { // 如果读通道处于空闲状态,说明没有接收到心跳命令
if (idle_count > 2) {
System.out.println("超过两次无客户端请求,关闭该channel");
ctx.channel().close();
} System.out.println("已等待5秒还没收到客户端发来的消息");
idle_count++;
}
} else {
super.userEventTriggered(ctx, obj);
}
} /**
* 业务逻辑处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("第" + count + "次" + ",服务端收到的消息:" + msg); String message = (String) msg;
// 如果是心跳命令,服务端收到命令后回复一个相同的命令给客户端
if ("hb_request".equals(message)) {
ctx.write("服务端成功收到心跳信息");
ctx.flush();
} count++;
} /**
* 异常处理
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

b、客户端
HeartNettyClient——客户端启动类

package com.dxfx.netty.demo;

import java.io.IOException;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; /**
* 客户端启动类
*
* @author Administrator
*
*/
public class HeartNettyClient { public static void main(String[] args) throws InterruptedException, IOException {
// 首先,netty通过Bootstrap启动客户端
Bootstrap client = new Bootstrap(); // 第1步 定义线程组,处理读写和链接事件,没有了accept事件
EventLoopGroup group = new NioEventLoopGroup();
client.group(group); // 第2步 绑定客户端通道
client.channel(NioSocketChannel.class); // 第3步 给NIoSocketChannel初始化handler, 处理读写事件
client.handler(new HeartNettyClientFilter()); // 连接服务端
Channel future = client.connect("localhost", 8080).sync().channel(); //给服务端发送数据
String str = "Hello Netty";
future.writeAndFlush(str);
System.out.println("客户端发送数据:" + str);
} }

HeartNettyClientFilter——客户端过滤器,如编解码和心跳的设置

package com.dxfx.netty.demo;

import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler; /**
* 客户端过滤器,如编解码和心跳的设置
*
* @author Administrator
*
*/
public class HeartNettyClientFilter extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
//因为服务端设置的超时时间是5秒,所以客户端设置4秒
ph.addLast(new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS)); // 解码和编码,应和服务端一致
ph.addLast(new StringDecoder());
ph.addLast(new StringEncoder()); //处理客户端的业务逻辑
ph.addLast(new HeartNettyClientHandler());
}
}

HeartNettyClientHandler——处理客户端业务逻辑:心跳超时处理、服务端返回的数据处理

package com.dxfx.netty.demo;

import java.text.SimpleDateFormat;
import java.util.Date; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil; /**
* 处理客户端业务逻辑:心跳超时处理、服务端返回的数据处理
*
* @author Administrator
*
*/
public class HeartNettyClientHandler extends ChannelInboundHandlerAdapter {
/** 客户端请求的心跳命令 */
private static final ByteBuf HEARTBEAT_SEQUENCE =
Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("hb_request", CharsetUtil.UTF_8)); /** 空闲次数 */
private int idle_count = 1; /** 发送次数 */
private int count = 1; /** 循环次数 */
private int fcount = 1; /**
* 建立连接时
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("建立连接时:" + date());
ctx.fireChannelActive();
} /**
* 关闭连接时
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("关闭连接时:" + date());
} /**
* 心跳请求处理,每4秒发送一次心跳请求;
*
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object obj) throws Exception {
System.out.println("\r\n循环请求的时间:" + date() + ",次数" + fcount); if (obj instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) obj;
if (IdleState.WRITER_IDLE.equals(event.state())) { // 如果写通道处于空闲状态就发送心跳命令
// 设置发送次数,允许发送3次心跳包
if (idle_count <= 3) {
idle_count++;
ctx.channel().writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
} else {
System.out.println("心跳包发送结束,不再发送心跳请求!!!");
}
}
} fcount++;
} /**
* 业务逻辑处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("第" + count + "次" + ",客户端收到的消息:" + msg);
count++;
} private String date(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
}
}

c、客户端输出信息

客户端发送数据:Hello Netty
建立连接时:2018-12-14 22:18:05 循环请求的时间:2018-12-14 22:18:09,次数1
第1次,客户端收到的消息:服务端成功收到心跳信息 循环请求的时间:2018-12-14 22:18:13,次数2
第2次,客户端收到的消息:服务端成功收到心跳信息 循环请求的时间:2018-12-14 22:18:17,次数3
第3次,客户端收到的消息:服务端成功收到心跳信息 循环请求的时间:2018-12-14 22:18:21,次数4
心跳包发送结束,不再发送心跳请求!!! 循环请求的时间:2018-12-14 22:18:25,次数5
心跳包发送结束,不再发送心跳请求!!! 循环请求的时间:2018-12-14 22:18:29,次数6
心跳包发送结束,不再发送心跳请求!!!
关闭连接时:2018-12-14 22:18:32

d、服务端输出信息

第1次,服务端收到的消息:Hello Netty
第2次,服务端收到的消息:hb_request
第3次,服务端收到的消息:hb_request
第4次,服务端收到的消息:hb_request
已等待5秒还没收到客户端发来的消息
已等待5秒还没收到客户端发来的消息
超过两次无客户端请求,关闭该channel
已等待5秒还没收到客户端发来的消息

Netty心跳机制的更多相关文章

  1. netty心跳机制测试

    netty中有比较完善的心跳机制,(在基础server版本基础上[netty基础--基本收发])添加少量代码即可实现对心跳的监测和处理. 1 server端channel中加入心跳处理机制 // Id ...

  2. 连接管理 与 Netty 心跳机制

    一.前言 踏踏实实,动手去做,talk is cheap, show me the code.先介绍下基础知识,然后做个心跳机制的Demo. 二.连接 长连接:在整个通讯过程,客户端和服务端只用一个S ...

  3. NETTY 心跳机制

    最近工作比较忙,但闲暇之余还是看了阿里的冯家春(fengjiachun)的github上的开源代码Jupiter,写的RPC框架让我感叹人外有人,废话不多说,下面的代码全部截取自Jupiter,写了一 ...

  4. netty心跳机制和断线重连(四)

    心跳是为了保证客户端和服务端的通信可用.因为各种原因客户端和服务端不能及时响应和接收信息.比如网络断开,停电 或者是客户端/服务端 高负载. 所以每隔一段时间 客户端发送心跳包到客户端  服务端做出心 ...

  5. netty心跳机制解决

    直接看别个的源码:https://blog.csdn.net/xt8469/article/details/84827443>>https://blog.csdn.net/xt8469/a ...

  6. Netty实现心跳机制

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

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

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

  8. 基于netty实现的长连接,心跳机制及重连机制

    技术:maven3.0.5 + netty4.1.33 + jdk1.8   概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

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

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

随机推荐

  1. Linux下搭建ftp服务

    Linux下ftp服务可以通过搭建vsftpd服务来实现,以CentOS为例,首先查看系统中是否安装了vsftpd,可以通过执行命令 rpm -qa | grep vsftpd 来查看是否安装相应的包 ...

  2. 如何学好游戏3D引擎编程

    注:本文是网上看到的一篇文章,感觉写的很好,因此收藏了下来 <如何学好游戏3D引擎编程>此篇文章献给那些为了游戏编程不怕困难的热血青年,它的神秘要我永远不间断的去挑战自我,超越自我,这样才 ...

  3. MPLAB X IDE V4.15 创建工程,编译,问题处理

    初步接触,有错误的地方还请大神们务必提出来,防止误导他人 硬件环境:MCU--PIC18F67K22 仿真下载器--ICD 3 编译环境:MPLAB X IDE V4.15 中文版 工作需要接触到了P ...

  4. HTTP协议简单认识

    一.HTTP协议简介 HTTP超文本传输协议是一种用于分布式,协作式和超媒体信息系统的应用层协议 二.HTTP协议概述 HTTP是一个客户端和服务端请求和响应的标准 三.HTTP协议工作步骤 1.客户 ...

  5. ubuntu上vsftpd服务配置

    Ubuntu上提供两种常用的ftp服务应用:vsftpd 和 tftpd,区别如下: 1)vsftpd 支持客户端上下传文件,支持浏览器显示及下载,支持用户名密码认证,支持匿名访问,默认端口TCP:2 ...

  6. ASP.NET对大文件上传的解决方案

    在ASP.NET 开发的过程中,最大的问题就在于上传大文件时让开发者尤为的头疼,而且,上传时无法方便的做到多线程的操控和上传进度的显示.笔者在此给大家推荐一款简单易用的上传组件,从而快速便捷得解决了 ...

  7. Nginx Redirect Websocket

    I want to redirect my websocket to another server. As we known, nginx command rewrite or redirect ca ...

  8. Visual Studio Code 写Python 代码

    最近在博客园新闻里面看到微软发布的Visual Studio Code 挺好用的,现在在学习Python,查看官网发布的VSCode 是支持Python代码,自己试着安装用一下,下面是我的安装以及配置 ...

  9. java web 乱码终结

    配置 tomcat 打开 tomcat 安装路径下的 conf/server.xml 文件,将 port 为 8080 的 connector 做如下更改: <Connector port=&q ...

  10. 微信小程序 web-view 的 url 带参问题

    在微信小程序开发过程中,会需要跳转到外部链接,微信提供了 <web-view>组件供我们使用. 为减少重复代码,一般会将这个功能单独抽取为一个页面供大家使用: <template&g ...