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

心跳是为了保证客户端和服务端的通信可用。因为各种原因客户端和服务端不能及时响应和接收信息。比如网络断开,停电 或者是客户端/服务端 高负载。

所以每隔一段时间 客户端发送心跳包到客户端  服务端做出心跳的响应;

1.如果客户端在指定时间没有向服务端发送心跳包。则表示客户端的通信出现了问题。

2.如果客户端发送心跳包到服务端没有收到响应 则表示服务端的通信出现了问题。

netty提供IdleStateHandle 在监听距离上一次写的时间和距离上一次读的时间 如果超时则调用

源码:

    

public class IdleStateHandler extends ChannelDuplexHandler 
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    // This method will be invoked only if this handler was added
    // before channelActive() event is fired.  If a user adds this handler
    // after the channelActive() event, initialize() will be called by beforeAdd().
    initialize(ctx);
    super.channelActive(ctx);
}  
}
 private void initialize(ChannelHandlerContext ctx) {
        // Avoid the case where destroy() is called before scheduling timeouts.
        // See: https://github.com/netty/netty/issues/143
        switch (state) {
        case 1:
        case 2:
            return;
        }

        state = 1;
        initOutputChanged(ctx);

        lastReadTime = lastWriteTime = ticksInNanos();
        if (readerIdleTimeNanos > 0) {
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),//监听read的task
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (writerIdleTimeNanos > 0) {
            writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),//监听写的task
                    writerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (allIdleTimeNanos > 0) {
            allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),//监听读写的task
                    allIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
    }
 private final class ReaderIdleTimeoutTask extends AbstractIdleTask {

        ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {
            long nextDelay = readerIdleTimeNanos;
            if (!reading) {
                nextDelay -= ticksInNanos() - lastReadTime;
            }

            if (nextDelay <= 0) {
                // Reader is idle - set a new timeout and notify the callback.
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                    IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Read occurred before the timeout - set a new timeout with shorter delay.
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

    private final class WriterIdleTimeoutTask extends AbstractIdleTask {

        WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {

            long lastWriteTime = IdleStateHandler.this.lastWriteTime;
            long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
            if (nextDelay <= 0) {
                // Writer is idle - set a new timeout and notify the callback.
                writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstWriterIdleEvent;
                firstWriterIdleEvent = false;

                try {
                    if (hasOutputChanged(ctx, first)) {
                        return;
                    }

                    IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Write occurred before the timeout - set a new timeout with shorter delay.
                writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

    private final class AllIdleTimeoutTask extends AbstractIdleTask {

        AllIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {

            long nextDelay = allIdleTimeNanos;
            if (!reading) {
                nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
            }
            if (nextDelay <= 0) {
                // Both reader and writer are idle - set a new timeout and
                // notify the callback.
                allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstAllIdleEvent;
                firstAllIdleEvent = false;

                try {
                    if (hasOutputChanged(ctx, first)) {
                        return;
                    }

                    IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Either read or write occurred before the timeout - set a new
                // timeout with shorter delay.
                allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

三个内部类是IdleSateHandle的内部类 可以看到内部是通过另起一个线程进行监听上一次对应事件的触发 如果超时则调用对应的事件

基于三的代码进行修改

首先是MessageHead消息头增加消息类型

public class MessageHead {
     private int headData=0X76;//协议开始标志
        private int length;//包的长度
        private String token;
        private Date createDate;
        private String type;//消息类型  ping表示心跳包
        public int getHeadData() {
            return headData;
        }
        public void setHeadData(int headData) {
            this.headData = headData;
        }
        public int getLength() {
            return length;
        }
        public void setLength(int length) {
            this.length = length;
        }
        
        
        public String getToken() {
            return token;
        }
        public void setToken(String token) {
            this.token = token;
        }
        public Date getCreateDate() {
            return createDate;
        }
        public void setCreateDate(Date createDate) {
            this.createDate = createDate;
        }
        
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
        @Override
        public String toString() {
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            // TODO Auto-generated method stub
            return "headData:"+headData+",length:"+length+",token:"+token+",createDate:"+    simpleDateFormat.format(createDate);
        }
}

MessageDecode

package com.liqiang.SimpeEcode;

import java.text.SimpleDateFormat;
import java.util.List;
import com.liqiang.nettyTest2.nettyMain;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageDecoder;

public class MessageDecode extends ByteToMessageDecoder{

    private final int BASE_LENGTH=4+4+50+50+50;//协议头 类型 int+length 4个字节+消息类型加令牌和 令牌生成时间50个字节
    private int headData=0X76;//协议开始标志
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        // 刻度长度必须大于基本长度
        if(buffer.readableBytes()>=BASE_LENGTH) {
            /**
             * 粘包 发送频繁 可能多次发送黏在一起 需要考虑  不过一个客户端发送太频繁也可以推断是否是攻击
             */
            //防止soket流攻击。客户端传过来的数据太大不合理
            if(buffer.readableBytes()>2048) {
                //buffer.skipBytes(buffer.readableBytes());
                
            }
        }
        int beginIndex;//记录包开始位置
        while(true) {
              // 获取包头开始的index  
            beginIndex = buffer.readerIndex();  
            //如果读到开始标记位置 结束读取避免拆包和粘包
            if(buffer.readInt()==headData) {
                break;
            }
             
            //初始化读的index为0
            buffer.resetReaderIndex();  
            // 当略过,一个字节之后,  
            //如果当前buffer数据小于基础数据 返回等待下一次读取
            if (buffer.readableBytes() < BASE_LENGTH) {  
                return;  
            }  
        }
           // 消息的长度  
        int length = buffer.readInt();  
        // 判断请求数据包数据是否到齐   -150是消息头的长度。
        if ((buffer.readableBytes()-150) < length) {  
            //没有到齐 返回读的指针 等待下一次数据到期再读
            buffer.readerIndex(beginIndex);  
            return;  
        }  
        //读取消息类型
        byte[] typeByte=new byte[50];
        buffer.readBytes(typeByte);
        //读取令牌
        byte[] tokenByte=new byte[50];
        buffer.readBytes(tokenByte);
       
        //读取令牌生成时间
        byte[]createDateByte=new byte[50];
        buffer.readBytes(createDateByte);
        //读取content
        byte[] data = new byte[length];  
        buffer.readBytes(data); 
        MessageHead head=new MessageHead();
        head.setHeadData(headData);
        head.setToken(new String(tokenByte).trim());
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        head.setCreateDate(  simpleDateFormat.parse(new String(createDateByte).trim()));
        head.setLength(length);
        head.setType(new String(typeByte).trim());
        Message message=new Message(head, data);
        //认证不通过
        if(!message.authorization(message.buidToken())) {
            ctx.close();
            
            return;
        }
        out.add(message);
        buffer.discardReadBytes();//回收已读字节
    }
    

}

MessageEncoder

package com.liqiang.SimpeEcode;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;

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

public class MessageEncoder extends MessageToByteEncoder<Message> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        // TODO Auto-generated method stub
        // 写入开头的标志
        out.writeInt(msg.getHead().getHeadData());
        // 写入包的的长度
        out.writeInt(msg.getContent().length);
        byte[] typeByte = new byte[50];
        /**
         * type定长50个字节
         *  第一个参数 原数组
         *  第二个参数 原数组位置
         *  第三个参数 目标数组 
         *  第四个参数 目标数组位置 
         *  第五个参数 copy多少个长度
         */
        byte[] indexByte=msg.getHead().getType().getBytes();
        try {
            System.arraycopy(indexByte, 0, typeByte, 0,indexByte.length>typeByte.length?typeByte.length:indexByte.length);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        //写入消息类型
        out.writeBytes(typeByte);
        byte[] tokenByte = new byte[50];
        /**
         * token定长50个字节
         *  第一个参数 原数组
         *  第二个参数 原数组位置
         *  第三个参数 目标数组 
         *  第四个参数 目标数组位置 
         *  第五个参数 copy多少个长度
         */
         indexByte=msg.getHead().getToken().getBytes();
        try {
            System.arraycopy(indexByte, 0, tokenByte, 0,indexByte.length>tokenByte.length?tokenByte.length:indexByte.length);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        
        //写入令牌
        out.writeBytes(tokenByte);
        byte[] createTimeByte = new byte[50];
        SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = format0.format(msg.getHead().getCreateDate());
        indexByte=time.getBytes();
        System.arraycopy(indexByte, 0, createTimeByte, 0,indexByte.length>createTimeByte.length?createTimeByte.length:indexByte.length);
        //写入令牌生成时间
        out.writeBytes(createTimeByte);
    
        // 写入消息主体
        out.writeBytes(msg.getContent());

    }

}

红色部分为改动部分

ClientChannelInitializer

package com.liqiang.nettyTest2;

import java.util.concurrent.TimeUnit;

import com.liqiang.SimpeEcode.MessageDecode;
import com.liqiang.SimpeEcode.MessageEncoder;

import io.netty.channel.ChannelInitializer;
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;

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {

    private Client client;
    public  ClientChannelInitializer(Client client) {
        // TODO Auto-generated constructor stub
        this.client=client;
    }
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // TODO Auto-generated method stub
        socketChannel.pipeline()
        //表示5秒向服务器发送一次心跳包   10秒没接收到服务器端信息表示服务器端通信异常 则会触发clientHandle userEventTriggered事件
         .addLast("ping",new IdleStateHandler(10, 5, 0, TimeUnit.SECONDS))
        .addLast("decoder",new MessageEncoder())
        .addLast("encoder",new MessageDecode())
        .addLast(new ClientHandle(client));//注册处理器
        
    }
}

ClientHandle修改

package com.liqiang.nettyTest2;

import java.util.Date;

import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead;

import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

public class ClientHandle extends ChannelInboundHandlerAdapter {
    
    Client client;
    public  ClientHandle(Client client) {
        // TODO Auto-generated constructor stub
       this.client=client;
    }
    /**
     * 读写超时事事件
* IdleStateHandle配置的 如果5秒没有触发writer事件 则会触发 userEventTrigerd方法 我们则写一次心跳
* 如果10秒没有触发read事件则表示服务器通信异常 因为我们每次发送一次心跳包 服务器都会做出对应的心跳反应 *
@throws Exception */ @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if(evt instanceof IdleStateEvent) { IdleStateEvent idleStateEvent=((IdleStateEvent) evt); /** * 如果没有收到服务端的写 则表示服务器超时 判断是否断开连接 */ if(idleStateEvent.state()==IdleState.READER_IDLE) { System.out.println("服务器无响应"); if(!ctx.channel().isOpen()) { System.out.println("正在重连"); client.connection(); System.out.println("重连成功"); } }else if(idleStateEvent.state()==IdleState.WRITER_IDLE) { //如果没有触发写事件则向服务器发送一次心跳包 System.out.println("正在向服务端发送心跳包"); MessageHead head=new MessageHead(); byte[]content="".getBytes(); head.setCreateDate(new Date()); head.setType("ping"); head.setLength(content.length); Message pingMessage=new Message(head,content); head.setToken(pingMessage.buidToken()); ctx.writeAndFlush(pingMessage); } }else { super.userEventTriggered(ctx, evt); } } //建立连接时回调 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // TODO Auto-generated method stub //System.out.println("与服务器建立连接成功"); client.setServerChannel(ctx); client.setConnection(true); //ctx.fireChannelActive();//如果注册多个handle 下一个handel的事件需要触发需要调用这个方法 } //读取服务器发送信息时回调 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Message message=(Message) msg; if(message.getHead().getType().equals("ping")) { //表示是心跳包 不做任何业务处理 }else { // TODO Auto-generated method stub System.out.println(msg.toString()); } } //发生异常时回调 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // TODO Auto-generated method stub System.out.println("发生异常 与服务器断开连接"); ctx.close();//关闭连接 } }

ServerChannelInitializer

package com.liqiang.nettyTest2;

import java.util.concurrent.TimeUnit;

import com.liqiang.SimpeEcode.MessageDecode;
import com.liqiang.SimpeEcode.MessageEncoder;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    private Server server;
    public ServerChannelInitializer(Server server) {
        this.server=server;
    }
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        // TODO Auto-generated method stub
        channel.pipeline()
        //7秒没收到客户端信息 则表示客户端因为网络等原因异常关闭 
        .addLast("ping",new IdleStateHandler(7, 0, 0,TimeUnit.SECONDS))
        .addLast("decoder",new MessageDecode())
        .addLast("encoder",new MessageEncoder())
        .addLast(new ServerHandle(server));
    }

}

ServerHandle

package com.liqiang.nettyTest2;

import java.util.Date;

import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead;

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.handler.timeout.IdleStateHandler;

public class ServerHandle extends ChannelInboundHandlerAdapter {

    private Server server;

    public ServerHandle(Server server) {
        // TODO Auto-generated constructor stub
        this.server = server;
    }
    /**
     * 读写超时事事件
     */
    @Override
    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("有客户端超时了");
                    ctx.channel().close();//关闭连接
            }
        }else {
            super.userEventTriggered(ctx, evt);
        }
        
    }

    // 建立连接时回调
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("有客户端建立连接了");
        server.addClient(ctx);
        // ctx.fireChannelActive();//pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
    }

    // 接收到客户端发送消息时回调
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message message=(Message)msg;
        if(message.getHead().getType().equals("ping")) {
            //表示心跳包 服务端响应心跳包  而不做相关业务处理
            MessageHead head=new MessageHead();
            byte[] content="".getBytes();
            head.setCreateDate(new Date());
            head.setType("ping");
            head.setLength(content.length);
            Message pingMessage=new Message(head,content);
            head.setToken(pingMessage.buidToken());
             ctx.writeAndFlush(pingMessage);
        }else {
            System.out.println("server接收到客户端发送信息:" + msg.toString());
        }
        // TODO Auto-generated method stub
        
        // ctx.fireChannelRead(msg);pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
    }

    // 通信过程中发生异常回调
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // TODO Auto-generated method stub
        // super.exceptionCaught(ctx, cause);
        ctx.close();// 发生异常关闭通信通道
        System.out.println("发生异常与客户端失去连接");
       
        cause.printStackTrace();
        // ctx.fireExceptionCaught(cause);pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
    }
}

client

package com.liqiang.nettyTest2;

import com.liqiang.SimpeEcode.Message;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.EventExecutorGroup;

public class Client implements Runnable{
    private String ip;// ip
    private int port;// 端口
    private boolean isConnection = false;
    private ChannelHandlerContext serverChannel;

    public Client(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    // 与服务器建立连接
    public void connection() {
        new Thread(this).start();
        
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        EventLoopGroup group = new NioEventLoopGroup();// 服务器监听服务器发送信息
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                .handler(new ClientChannelInitializer(this));// 基于NIO编程模型通信
        try {
            ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();

            channelFuture.channel().closeFuture().sync(); 
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println("连接服务器失败");
        }finally {
            //尝试重连
            System.out.println("正在重连");
            run();
        }    
    }

    public void close() {
        serverChannel.close();
    }
    public boolean isConnection() {
        return isConnection;
    }

    public void setConnection(boolean isConnection) {
        this.isConnection = isConnection;
    }

    public void sendMsg(Message msg) {
        while(isConnection) {
            serverChannel.writeAndFlush(msg);
        }
        
    }

    public ChannelHandlerContext getServerChannel() {
        return serverChannel;
    }

    public void setServerChannel(ChannelHandlerContext serverChannel) {
        this.serverChannel = serverChannel;
    }

}

Server

package com.liqiang.nettyTest2;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Vector;

import com.liqiang.SimpeEcode.Message;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class Server implements Runnable {
    private int port;// 监听端口
    private Vector<ChannelHandlerContext> clients;// 保存在线客户端信息

    public Server(int port) {
        clients = new Vector<ChannelHandlerContext>();
        this.port = port;
    }

    // 广播
    public void sendAll(Message msg) {
        clients.forEach(c -> {
            c.writeAndFlush(msg);
        });
    }

    public void addClient(ChannelHandlerContext client) {
        clients.add(client);
    }

    @Override
    public void run() {
        /**
         * NioEventLoopGroup 内部维护一个线程池 如果构造函数没有指定线程池数量 则默认为系统core*2
         */
        EventLoopGroup acceptor = new NioEventLoopGroup();// acceptor负责监客户端连接请求
        EventLoopGroup worker = new NioEventLoopGroup();// worker负责io读写(监听注册channel的 read/writer事件)

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(acceptor, worker).channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(port)).childHandler(new ServerChannelInitializer(this))
                .option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
        try {
            ChannelFuture channelFuture = bootstrap.bind(port).sync();

            System.out.println("服务器已启动");
            // 将阻塞 直到服务器端关闭或者手动调用
             channelFuture.channel().closeFuture().sync();
            // 释放资源
            
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
                        acceptor.shutdownGracefully();
                        worker.shutdownGracefully();
        }

    }

    public void startServer() {
        new Thread(this).start();
    }

}

测试

package com.liqiang.nettyTest2;

import java.util.Date;

import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead;

public class nettyClientMain {
    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                Client client1 = new Client("127.0.0.1", 8081);
                client1.connection();
                String content = "哈哈哈哈!";
                byte[] bts = content.getBytes();
                MessageHead head = new MessageHead();
                // 令牌生成时间
                head.setCreateDate(new Date());
                head.setType("message");
                head.setLength(bts.length);
                Message message = new Message(head, bts);    
                message.getHead().setToken(message.buidToken());
                client1.sendMsg(message);
                

            }
        }).start();
        
    }
}
package com.liqiang.nettyTest2;

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

import javax.management.StringValueExp;
import javax.swing.text.StringContent;

import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead;

public class nettyMain {
    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                Server server = new Server(8081);
                server.startServer();

            }
        }).start();
    
    }

}

1.先开启服务端

2.再开启客户端

 3.关闭服务端

然后我们再重新启动服务端 打印

正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在向服务端发送心跳包
正在向服务端发送心跳包
正在向服务端发送心跳包
正在向服务端发送心跳包

猜你喜欢

转载自www.cnblogs.com/LQBlog/p/9163424.html