Netty教程入门(二),Netty实现长连接,心跳检测,收发消息

引入依赖

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.17.Final</version>
        </dependency>
        <!-- 解码and编码器 -->
        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>

解码器

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;

import java.util.List;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:20
 */
public class MessageDecode extends MessageToMessageDecoder<ByteBuf> {
    
    

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg,
                          List<Object> out) throws Exception {
    
    
        final byte[] array;
        //获取可读取的字节数
        final int length = msg.readableBytes();
        //存放数据的字节数组
        array = new byte[length];
        //读取数据到array数组中
        msg.getBytes(msg.readerIndex(), array, 0, length);
        MessagePack pack = new MessagePack();
        //转化为java对象
        out.add(pack.read(array, NettyMessage.class));

    }
}

编码器

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:24
 */
public class MessageEncode extends MessageToByteEncoder<NettyMessage> {
    
    

    @Override
    protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf buf) throws Exception {
    
    
        MessagePack pack = new MessagePack();
        //把java对象转化为字节
        byte[] write = pack.write(msg);
        buf.writeBytes(write);

    }
}

业务逻辑处理器公共类ChannelHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:30
 */
public abstract class MessageHandler extends ChannelInboundHandlerAdapter {
    
    

    protected String name;
    //记录次数
    private int heartbeatCount = 0;

    //获取server and client 传入的值
    public MessageHandler(String name) {
    
    
        this.name = name;
    }
    /**
     * 输入数据业务逻辑处理
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        NettyMessage m = (NettyMessage) msg;
        int type = m.getType();
        switch (type) {
    
    
            case 1:
                //收到心跳消息,进行响应
                sendPongMsg(ctx);
                break;
            case 2:
                //发送心跳消息后收到响应
                System.out.println(name + " 收到心跳响应消息 " + ctx.channel().remoteAddress());
                break;
            case 3:
                handlerData(ctx,msg);
                break;
            default:
                break;
        }
    }

    protected abstract void handlerData(ChannelHandlerContext ctx,Object msg);

    protected void sendPingMsg(ChannelHandlerContext ctx){
    
    
        NettyMessage message = new NettyMessage();

        message.setType(MessageType.PING);

        ctx.channel().writeAndFlush(message);

        heartbeatCount++;

        System.out.println(name + " 发送心跳消息 " + ctx.channel().remoteAddress() + "count :" + heartbeatCount);
    }

    private void sendPongMsg(ChannelHandlerContext ctx) {
    
    

        NettyMessage message = new NettyMessage();

        message.setType(MessageType.PONG);

        ctx.channel().writeAndFlush(message);

        heartbeatCount++;

        System.out.println(name +" 收到心跳发送响应消息 "+ctx.channel().remoteAddress() +" , count :" + heartbeatCount);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
            throws Exception {
    
    
        IdleStateEvent stateEvent = (IdleStateEvent) evt;

        switch (stateEvent.state()) {
    
    
            case READER_IDLE:
                handlerReaderIdle(ctx);
                break;
            case WRITER_IDLE:
                handlerWriterIdle(ctx);
                break;
            case ALL_IDLE:
                handlerAllIdle(ctx);
                break;
            default:
                break;
        }
    }

    /**
     * 客户端与服务端在指定时间内没有任何读写请求,就会认为连接是idle的
     *
     * @param ctx
     */
    protected void handlerAllIdle(ChannelHandlerContext ctx) {
    
    
        System.err.println("---ALL_IDLE---");
    }

    protected void handlerWriterIdle(ChannelHandlerContext ctx) {
    
    
        System.err.println("---WRITER_IDLE---");
    }


    protected void handlerReaderIdle(ChannelHandlerContext ctx) {
    
    
        System.err.println("---READER_IDLE---");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.err.println(" ---"+ctx.channel().remoteAddress() +"----- is  active" );
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.err.println(" ---"+ctx.channel().remoteAddress() +"----- is  no active");
    }
}

定义消息类型

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:17
 */
public interface MessageType {
    
    
    //心跳消息
    byte PING = 1;
    //响应消息
    byte PONG = 2;
    //业务消息
    byte CUSTOMER = 3;

}

定义消息对象

import lombok.Data;
import org.msgpack.annotation.Message;

import java.io.Serializable;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:16
 */
@Data
@Message
public class NettyMessage implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    // 消息类型
    private int type;

    // 消息内容
    private String body;
    
}

Server端消息处理器

import com.example.springbootnetty.longconection.NettyMessage;
import com.example.springbootnetty.longconection.MessageType;
import com.example.springbootnetty.longconection.MessageHandler;
import io.netty.channel.ChannelHandlerContext;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:34
 */
public class ServerHandler extends MessageHandler {
    
    

    public ServerHandler() {
    
    
        super("server");
    }
    @Override
    protected void handlerData(ChannelHandlerContext ctx, Object msg) {
    
    
        NettyMessage message  = (NettyMessage) msg;
        System.out.println("server 接收业务请求并进行业务逻辑处理 : " +  message.toString());
        message.setType(MessageType.CUSTOMER);
        message.setBody("server 业务逻辑处理结果");
        ctx.channel().writeAndFlush(message);
        System.out.println("server 发送业务逻辑处理结果: " + message.toString());
    }
    @Override
    protected void handlerReaderIdle(ChannelHandlerContext ctx) {
    
    
        super.handlerReaderIdle(ctx);
        System.err.println(" ---- client "+ ctx.channel().remoteAddress().toString() + " reader timeOut, --- close it");
        ctx.close();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
    
    
        System.err.println( name +"  exception" + cause.toString());
    }
}

Server服务器

import com.example.springbootnetty.longconection.MessageDecode;
import com.example.springbootnetty.longconection.MessageEncode;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:38
 */
public class Server {
    
    
    public static void main(String[] args) {
    
    
        EventLoopGroup bossGroup = new NioEventLoopGroup();

        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        try {
    
    
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(8081)
                    .childHandler(new ChannelInitializer<Channel>() {
    
    

                        @Override
                        protected void initChannel(Channel ch) throws Exception {
    
    
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new IdleStateHandler(10,0,0));
                            pipeline.addLast(new MessageDecode());
                            pipeline.addLast(new MessageEncode());
                            pipeline.addLast(new ServerHandler());
                        }
                    });
            System.out.println("start server 8081 --");
            ChannelFuture sync = serverBootstrap.bind().sync();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
    
    
            //优雅的关闭资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

client端消息处理器

import com.example.springbootnetty.longconection.MessageHandler;
import com.example.springbootnetty.longconection.NettyMessage;
import io.netty.channel.ChannelHandlerContext;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:33
 */
public class ClientHandler extends MessageHandler {
    
    

    private Client client;

    public ClientHandler(Client client) {
    
    
        super("client");
        this.client = client;
    }

    @Override
    protected void handlerData(ChannelHandlerContext ctx, Object msg) {
    
    
        NettyMessage message = (NettyMessage) msg;
        System.out.println("client  收到 server 业务逻辑处理结果: " + message.toString());
    }
    @Override
    protected void handlerAllIdle(ChannelHandlerContext ctx) {
    
    
        super.handlerAllIdle(ctx);
        sendPingMsg(ctx);
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        super.channelInactive(ctx);
        client.doConnect();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
    
    
        System.out.println(name + "exception :"+ cause.toString());
    }
}

client客户端

import com.example.springbootnetty.longconection.MessageDecode;
import com.example.springbootnetty.longconection.MessageEncode;
import com.example.springbootnetty.longconection.MessageType;
import com.example.springbootnetty.longconection.NettyMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

/**
 * @author tom
 * @version V1.0
 * @date 2020/11/22 17:28
 */
public class Client {
    
    

    private NioEventLoopGroup worker = new NioEventLoopGroup();

    private Channel channel;

    private Bootstrap bootstrap;

    public static void main(String[] args) throws InterruptedException {
    
    
        Client client = new Client();
        client.start();
        client.sendData("Hello netty 这是一条业务处理请求");
    }

    private void start() {
    
    
        bootstrap = new Bootstrap();
        bootstrap.group(worker)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<Channel>() {
    
    
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
    
    
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new IdleStateHandler(0, 0, 5));
                        pipeline.addLast(new MessageDecode());
                        pipeline.addLast(new MessageEncode());
                        pipeline.addLast(new ClientHandler(Client.this));
                    }
                });
        doConnect();
    }

    /**
     * 连接服务端 and 重连
     */
    protected void doConnect() {
    
    
        if (channel != null && channel.isActive()) {
    
    
            return;
        }
        ChannelFuture connect = bootstrap.connect("127.0.0.1", 8081);
        // 实现监听通道连接的方法
        connect.addListener(new ChannelFutureListener() {
    
    
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
    
    
                if (channelFuture.isSuccess()) {
    
    
                    System.out.println("连接状态正常---------" + channel.isActive());
                } else {
    
    
                    System.out.println("每隔2s重连....");
                    channelFuture.channel().eventLoop().schedule(new Runnable() {
    
    
                        @Override
                        public void run() {
    
    
                            doConnect();
                        }
                    }, 2, TimeUnit.SECONDS);
                }
            }
        });
        channel = connect.channel(); // 作用域问题,分包处理会拿不到 channel,赋值一下下面的 sendData 才可以拿到正确的channel
    }

    /**
     * 向服务端发送消息
     */
    private void sendData(String msg) {
    
    
        try {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                Thread.sleep(5000);
                if (channel != null && channel.isActive()) {
    
    
                    //获取一个键盘扫描器
                    NettyMessage message = new NettyMessage();
                    message.setType(MessageType.CUSTOMER);
                    message.setBody(msg);
                    System.out.println("第" + (i + 1) + "次向server发送业务处理请求");
                    channel.writeAndFlush(message);
                }
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
            System.out.println("线程被中断");
        }
    }
}

先启动Server再启动client,client发送业务处理请求到Server端,Server端处理后返回业务逻辑处理结果给client,当最大空闲时间内客户端与服务端在指定时间内没有任何读写请求,会进行心跳检测

GitHub代码

https://github.com/JsonTom888/springboot-netty

猜你喜欢

转载自blog.csdn.net/weixin_43073775/article/details/109963565