前言:在实现过程查找过许多资料,各种波折,最后综合多篇文章最终实现并上线使用。为了减少大家踩坑的时间,所以写了本文,希望有用。对于实现过程中有用的参考资料直接放上链接,可能有些内容相对冗余,不过时间允许多看看也并不无益。

入门文章:

http://www.tuicool.com/articles/mEJvYb

netty官网:
(官网的user guide相对一般,javadoc倒是要看的)
 
需求场景:
实现用户的在线离线状态实时展现(我们的客户端是android)。
 
技术选型:
在线好办,关键是要监测到什么时候离线,于是我们选择了心跳模型,当心跳失效时即为离线。如果用http发送心跳包虽然简单但是极度不科学,耗电量太大,所以直接否决。我们选择基于TCP实现长连接,而借助一些第三方插件可以更好更快地实现长连接,于是在mina和netty之间我们选择了netty。(理由仅仅是在百度知道里边看到别人说netty使用的更广泛,没有深入对比过)
 
相关版本:
netty5.0
jdk1.7
tomcat6.0
 
基础流程图如下:
服务端代码:
HeartBeatServer.java
public class HeartBeatServer {

	// 端口
private int port ;
public HeartBeatServer(int port) {
this.port = port;
} ChannelFuture f ; ServerBootstrap b ; // 检测chanel是否接受过心跳数据时间间隔(单位秒)
private static final int READ_WAIT_SECONDS = 10; public void startServer() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.childHandler(new HeartBeatServerInitializer());
// 服务器绑定端口监听
f = b.bind(port).sync();
// 监听服务器关闭监听,此方法会阻塞
f.channel().closeFuture().sync();
// 可以简写为
/* b.bind(portNumber).sync().channel().closeFuture().sync(); */
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* 消息处理器
* @author cullen edward
*/
private class HeartBeatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); /*
* 使用ObjectDecoder和ObjectEncoder
* 因为双向都有写数据和读数据,所以这里需要两个都设置
* 如果只读,那么只需要ObjectDecoder即可
*/
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder()); /*
* 这里只监听读操作
* 可以根据需求,监听写操作和总得操作
*/
pipeline.addLast("pong", new IdleStateHandler(READ_WAIT_SECONDS, 0, 0,TimeUnit.SECONDS)); //pipeline.addLast("handler", new Heartbeat());
pipeline.addLast("handler", new HeartbeatHandler());
}
} public void stopServer(){
if(f!=null){
f.channel().close();
}
}
/**
* @param args
*/
public static void main(String[] args) {
HeartbeatServer heartbeatServer = new HeartbeatServer(9597);
heartbeatServer.startServer();
}
}

  

HeartbeatHandler.java
public class HeartbeatHandler extends SimpleChannelInboundHandler<String> {
// 失败计数器:未收到client端发送的ping请求
private int unRecPingTimes = 0 ;
private String userid; // 定义服务端没有收到心跳消息的最大次数
private static final int MAX_UN_REC_PING_TIMES = 3; @Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("----->msg=" + msg); //msg格式约定为"operation,userid"
String[] args = msg.split(",");
String msg_operation = args[0];
String msg_userid = args[1];
if("LOGIN".equals(msg_operation)){
if(!Utils.isBlank(msg_userid)){
userid = msg_userid;
}
setUserOnlineStatus(userid, true);
}else if("HEARTBEAT".equals(msg_operation)){
ctx.channel().writeAndFlush("success");
// 失败计数器清零
unRecPingTimes = 0;
}
} public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
/*读超时*/
System.out.println("===服务端===(READER_IDLE 读超时)");
// 失败计数器次数大于等于3次的时候,关闭链接,等待client重连
if(unRecPingTimes >= MAX_UN_REC_PING_TIMES){
System.out.println("===服务端===(读超时,关闭chanel)");
// 连续超过N次未收到client的ping消息,那么关闭该通道,等待client重连
ctx.channel().close();
}else{
// 失败计数器加1
unRecPingTimes++;
}
} else if (event.state() == IdleState.WRITER_IDLE) {
/*写超时*/
System.out.println("===服务端===(WRITER_IDLE 写超时)");
} else if (event.state() == IdleState.ALL_IDLE) {
/*总超时*/
System.out.println("===服务端===(ALL_IDLE 总超时)");
}
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("错误原因:"+cause.getMessage());
ctx.channel().close();
setUserOnlineStatus(userid, false);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client active ");
super.channelActive(ctx);
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 关闭,等待重连
ctx.close();
System.out.println("===服务端===(客户端失效)");
setUserOnlineStatus(userid, false);
} //设置用户在线离线状态
private void setUserOnlineStatus(String userid, boolean isOnline){
if(!Utils.isBlank(userid)){
//更新用户信息为在线状态(此处代码省略)
}
}
}

  

简易的测试客户端代码:

SimpleClient.java

public class SimpleClient {
public static void main(String[] args) throws Exception {
new SimpleClient("127.0.0.1", 9597).run();
}
private final String host;
private final int port;
public SimpleClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap().group(group).channel(
NioSocketChannel.class).handler(
new SimpleClientInitializer());
Channel channel = bootstrap.connect(host, port).sync().channel();
BufferedReader in = new BufferedReader(new InputStreamReader(
System.in));
while (true) {
channel.writeAndFlush(in.readLine());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}

  

SimpleClientInitializer.java

public class SimpleClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
}
}

备注:代码大部分是从其他网站复制修改调整的,写得相对简易一点,其中还有很多安全性、合理性有待优化。

代码参考文章:

 
更多相关文章:
 

基于netty的心跳机制实现的更多相关文章

  1. Netty实现心跳机制

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

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

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

  3. netty之心跳机制

    1.心跳机制,在netty3和netty5上面都有.但是写法有些不一样. 2.心跳机制在服务端和客户端的作用也是不一样的.对于服务端来说:就是定时清除那些因为某种原因在一定时间段内没有做指定操作的客户 ...

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

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

  5. Netty(六):Netty中的连接管理(心跳机制和定时断线重连)

    何为心跳 顾名思义, 所谓心跳, 即在TCP长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性. 为什么需要心跳 因为网络的不可靠性, 有可 ...

  6. 基于Netty的IdleStateHandler实现Mqtt心跳

    基于Netty的IdleStateHandler实现Mqtt心跳 IdleStateHandler解析 最近研究jetlinks编写的基于Netty的mqtt-client(https://githu ...

  7. Netty学习篇④-心跳机制及断线重连

    心跳检测 前言 客户端和服务端的连接属于socket连接,也属于长连接,往往会存在客户端在连接了服务端之后就没有任何操作了,但还是占用了一个连接:当越来越多类似的客户端出现就会浪费很多连接,netty ...

  8. 9.7 dubbo心跳机制

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

  9. Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议

    本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...

随机推荐

  1. Android开发之详解五大布局

    http://bbs.chinaunix.net/thread-3654213-1-1.html 为了适应各式各样的界面风格,Android系统提供了5种布局,这5种布局分别是: LinearLayo ...

  2. Sphinx 全量索引加实时索引

    source mysql { type = mysql sql_host = 10.10.3.181 sql_user = root sql_pass = dsideal sql_db = dside ...

  3. Codeforces Round #242 (Div. 2) B. Megacity

    按照半径排序,然后累加人数直到超过百万 #include <iostream> #include <algorithm> #include <cmath> #inc ...

  4. Android -- shape 定义控件的属性

    <shape> <!-- 实心 --> <solid android:color="#ff9d77"/> <!-- 渐变 --> & ...

  5. linux下常用关机命令

    linux下常用的关机命令有:shutdown.halt.poweroff.init:重启命令有:reboot.下面本文就主要介绍一些常用的关机命令以及各种关机命令之间的区别和具体用法. 首先来看一下 ...

  6. xcode 忽然无法真机调试

    手机升级了系统后一直没有再进行真机调试,今天要去面试把手机插上后忽然显示iPhone(unavailable),选中自己的设备后运行发现弹出警告could not find developer dis ...

  7. MyBatis的几种批量操作

    MyBatis中批量插入 方法一: <insert id="insertbatch" parameterType="java.util.List"> ...

  8. UWP ComboBox下拉选项滚动循环问题

    在UWP开发中遇到个小问题,ComboBox的Item太多的话,列表会重复,并且无限循环,Google了一下后发现这貌似是Metro应用的老问题了,由于ComboBox的Items使用的是Carous ...

  9. Hadoop.2.x_WebUV示例

    一.网站基本指标(即针对于网站用户行为而产生的日志中进行统计分析) 1. PV:网页浏览量(Page View页面浏览次数,只要进入该网页就产生一条记录,不限IP,统计点每天(较多)/每周/每月/.. ...

  10. val, lazy, def

    val strVal = scala.io.Source.fromFile("test.txt").mkString //在strVal被定义的时候获取值,如果test.txt不存 ...