netty(4)--netty的心跳检测

  • 客户端
package com.xiyou.netty3.heart;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;

import java.net.InetSocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * netty的客户端
 */
public class Client {
    public static void main(String[] args) {
        // 声明一个服务类
        ClientBootstrap bootstrap = new ClientBootstrap();
        // 声明两个线程池
        // 监听和服务端的连接
        ExecutorService boss = Executors.newCachedThreadPool();
        // 监听和服务端的数据的交互
        ExecutorService worker = Executors.newCachedThreadPool();
        // socket工厂
        bootstrap.setFactory(new NioClientSocketChannelFactory(boss, worker));
        // 声明管道工厂
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            public ChannelPipeline getPipeline() throws Exception {
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", new StringDecoder());
                pipeline.addLast("encoder", new StringEncoder());
                pipeline.addLast("clientHandler", new ClientHandler());
                return pipeline;
            }

        });

        // 连接服务端
        ChannelFuture connect = bootstrap.connect(new InetSocketAddress("127.0.0.1", 10101));
        System.out.println("客户端已经正常连接");

        Channel channel = connect.getChannel();
        Scanner scanner = new Scanner(System.in);
        while(true){
            System.out.println("请输入");
            String value = scanner.next();
            if("stop".equals(value)){
                break;
            }
            // 可以直接写字符串的原因就是设置了编码器,可以自动将字符串转换成ChannelBuffer
            channel.write(value);
        }
        System.out.println("我已经退出了客户端的发送....");
        try {
            channel.close().sync();
            System.exit(0);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

  • ClientHandler
package com.xiyou.netty3.heart;

import org.jboss.netty.channel.*;

import java.util.Scanner;

/**
 * 是管道中添加的过滤器
 */
public class ClientHandler extends SimpleChannelHandler {

    /**
     * 关闭连接,即使连接没有成功连接,我们也会在关闭的时候调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelClosed------");
        super.channelClosed(ctx, e);
    }

    /**
     * 连接建立成功的时候,我们就会调用该方法
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelConnected------");
        super.channelConnected(ctx, e);
    }

    /**
     * 关闭连接的时候会调用该方法(只有当连接建立成功后再关闭才会调用)
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
        System.out.println("------channelDisconnected------");
        super.channelDisconnected(ctx, e);
    }

    /**
     * messageRecived方法抛出异常的时候调用该方法。该方法用来捕获异常
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        System.out.println("------exceptionCaught------");
        super.exceptionCaught(ctx, e);
    }

    /**
     * 用来接收客户端发来的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        System.out.println("------messageReceived------: " + e.getMessage());
        super.messageReceived(ctx, e);
    }
}

上面的代码和netty3实现的helloworld中的代码没有区别,我们这里主要看看服务端的代码

  • ServerHandlerHeart
package com.xiyou.netty3.heart;

import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * IdleStateAwareChannelHandler 是工具类,用来监听连接状态的
 */
public class ServerHandlerHeart extends IdleStateAwareChannelHandler implements ChannelHandler {
    @Override
    public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception {
        SimpleDateFormat dateFormat = new SimpleDateFormat("ss");
        // 显示当前的会话状态
        System.out.println(e.getState() + " : " + dateFormat.format(new Date()));
    }
}

上面的类继承了IdleStateAwareChannelHandler ,该类的主要作用就是用来监听客户端的连接状态

  • Server
package com.xiyou.netty3.heart;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * netty的服务端
 * 可以利用telnet ip 端口号 进行调用
 * ctrl + ] 可以发送数据
 * 发送数据用 send XXX
 */
public class Server {
    public static void main(String[] args) {
        // 1.先创建一个服务类
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 2.创建两个线程池,一个负责监控客户端的连接,一个负责监控客户端发送的数据
        ExecutorService boss = Executors.newCachedThreadPool();
        ExecutorService worker = Executors.newCachedThreadPool();
        // 给服务类设置一个工厂
        // 将两个线程池传入
        // 设置NIO socket工厂
        bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
        // 新增一个定时器
        final HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
        // 设置NIO的管道的工厂
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

            public ChannelPipeline getPipeline() throws Exception {
                // 先声明一个管道
                ChannelPipeline pipeline = Channels.pipeline();


                // 需要有一个触发IdleStateAwareChannelHandler的过滤器
                // IdleStateHandler有四个参数,第一个是一个定时器
                // 第二个参数是规定多久时间没有读数据,抛出读数据的异常
                // 第三个参数是规定多久时间没有写数据,抛出写数据的异常
                // 第三个参数是规定多久时间没有进行读写数据,抛出读写数据的异常
                // 单位都是s
                pipeline.addLast("idle", new IdleStateHandler(hashedWheelTimer,
                        5,
                        7,
                        9));

                pipeline.addLast("stringEncoder", new StringEncoder());
                pipeline.addLast("stringDecoder", new StringDecoder());
                // 管道里装了一堆过滤器
                pipeline.addLast("helloHandler", new ServerHandlerHeart());
                return pipeline;
            }

        });

        // 用服务类绑定端口
        bootstrap.bind(new InetSocketAddress(10101));
        System.out.println("服务端已经启动!!!");
    }
}

这里我们抽取出重要的方法:

		// 新增一个定时器
        final HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();


                // 需要有一个触发IdleStateAwareChannelHandler的过滤器
                // IdleStateHandler有四个参数,第一个是一个定时器
                // 第二个参数是规定多久时间没有读数据,抛出读数据的异常
                // 第三个参数是规定多久时间没有写数据,抛出写数据的异常
                // 第三个参数是规定多久时间没有进行读写数据,抛出读写数据的异常
                // 单位都是s
                pipeline.addLast("idle", new IdleStateHandler(hashedWheelTimer,
                        5,
                        7,
                        9));

第一行我们新增了一个定时器,该定时器用来传入pipleLine的过滤器中,用来监听指定的时间内有没有对其进行读,写,读写操作,单位是s。如果在规定的时间内没有进行相应的操作,则会抛出异常触发handler中的channelIdle方法,我们在该方法中进行了打印,我们可以看到结果如下。

  • 结果是
服务端已经启动!!!
READER_IDLE : 06
WRITER_IDLE : 08
ALL_IDLE : 10

但是我们很快就发现了一个问题,我们继承的IdleStateAwareChannelHandler 这个类,并不能够对客户端发送的数据进行操作,即没有receiveMessage方法,经过我们的仔细研究,我们发现SimpleChannelHandler类中有一个handleUpstream方法可以获取IdleStateEvent事件,因此我们将代码进行修改。使其既可以监听发送的消息,又可以对连接进行监控。

  • 修改后的ServerHandler
public class ServerHandlerHeart extends SimpleChannelHandler{

    /**
     * 用来接收客户端的消息
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        System.out.println("接收到的客户端的信息是: " + e.getMessage());
        super.messageReceived(ctx, e);
    }

    /**
     * 该方法可以用来监听连接,判断是否超时未读,未写,未读写
     * @param ctx
     * @param e
     * @throws Exception
     */
    @Override
    public void handleUpstream(final ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        // 判断事件e是否是IdleStateEvent事件,如果是该事件,可以针对不同的时间进行处理
        if(e instanceof IdleStateEvent){
            /**
             * IdleState有状态
             * READER_IDLE, 读超时
             * WRITER_IDLE, 写超时
             * ALL_IDLE; 读写超时
             */
            if(((IdleStateEvent)e).getState() == IdleState.ALL_IDLE){
                System.out.println("长时间未读写,断开连接");
                // 关闭会话
                // 向客户端写数据,通知客户端,应该关闭连接
                ChannelFuture channel = ctx.getChannel().write("您长时间未读写,断开连接");
                // 给该Channel设置监听,数据写给客户端成功后,调用该方法
                channel.addListener(new ChannelFutureListener() {

                    // 回调方法
                    public void operationComplete(ChannelFuture channelFuture) throws Exception {
                        // 关闭客户端的连接
                        ctx.getChannel().close();
                    }

                });
            }
        }else{
            // 如果不是IdleStateEvent事件
            super.handleUpstream(ctx, e);
        }
    }
}

上面的handleUpstream方法会判断是否是IdleStateEvent事件,如果是该事件,监听是否是读写超时,如果是读写超时,断开连接,并给客户端通知,并设置监听事件,当给客户端发送完数据之后,调用该监听器的operationComplete方法,上面的代码中是关闭客户端的连接。

上面的代码是用netty3实现的,netty5将其方法名和类名进行了改变。
将handleUpstream方法改为userEventTriggered(ChannelHandlerContext ctx, Object evt)

  • Serverhandler
package com.heart;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
/**
 * 服务端消息处理
 * @author -琴兽-
 *
 */
public class ServerHandler extends SimpleChannelInboundHandler<String> {

	@Override
	protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {

		System.out.println(msg);
		
		ctx.channel().writeAndFlush("hi");
		ctx.writeAndFlush("hi");
	}
	
	

	@Override
	public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception {
		if(evt instanceof IdleStateEvent){
			IdleStateEvent event = (IdleStateEvent)evt;
			if(event.state() == IdleState.ALL_IDLE){
				
				//清除超时会话
				ChannelFuture writeAndFlush = ctx.writeAndFlush("you will close");
				writeAndFlush.addListener(new ChannelFutureListener() {
					
					@Override
					public void operationComplete(ChannelFuture future) throws Exception {
						ctx.channel().close();
					}
				});
			}
		}else{
			super.userEventTriggered(ctx, evt);
		}
	}


	/**
	 * 新客户端接入
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("channelActive");
	}

	/**
	 * 客户端断开
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("channelInactive");
	}

	/**
	 * 异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
	}
	
	
}

  • Server
package com.heart;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * netty5服务端
 * @author -琴兽-
 *
 */
public class Server {

	public static void main(String[] args) {
		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		
		//boss和worker
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup();
		
		try {
			//设置线程池
			bootstrap.group(boss, worker);
			
			//设置socket工厂、
			bootstrap.channel(NioServerSocketChannel.class);
			
			//设置管道工厂
			bootstrap.childHandler(new ChannelInitializer<Channel>() {

				@Override
				protected void initChannel(Channel ch) throws Exception {
					ch.pipeline().addLast(new IdleStateHandler(5, 5, 10));
					ch.pipeline().addLast(new StringDecoder());
					ch.pipeline().addLast(new StringEncoder());
					ch.pipeline().addLast(new ServerHandler());
				}
			});
			
			//netty3中对应设置如下
			//bootstrap.setOption("backlog", 1024);
			//bootstrap.setOption("tcpNoDelay", true);
			//bootstrap.setOption("keepAlive", true);
			//设置参数,TCP参数
			bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置,链接缓冲池的大小
			bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃,清除死链接
			bootstrap.childOption(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送
			
			//绑定端口
			ChannelFuture future = bootstrap.bind(10101);
			
			System.out.println("start");
			
			//等待服务端关闭
			future.channel().closeFuture().sync();
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			//释放资源
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}
}

总结下:

  1. IdleStateHandler
    该类的作用是用来检测会话的状态,包括读超时,写超时,读写超时。
  2. 什么是心跳
    心跳其实是一个请求而已,特点是数据简单,业务也简单,只是单纯为了看服务端和客户端连接是否正常。
    服务端:心跳对于服务端来说,定时清除闲置会话inactive(netty5)和channelclose(netty3)
    客户端:心跳对于客户端来说,用来检测会话是否断开,是否需要重连!用来检测网络延迟。

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/89214334