• 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.关闭服务端

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

    正在重连
    正在重连
    正在重连
    正在重连
    正在重连
    正在重连
    正在重连
    正在重连
    正在向服务端发送心跳包
    正在向服务端发送心跳包
    正在向服务端发送心跳包
    正在向服务端发送心跳包
  • 相关阅读:
    机器学习之逻辑回归
    机器学习之线性回归与模型保存
    机器学习之决策树
    机器学习之贝叶斯算法
    机器学习之KNN算法
    算法基础与开发流程
    特征选择与特征降维
    特征预处理
    RSA加密算法和签名算法
    Java中使用OpenSSL生成的RSA公私钥
  • 原文地址:https://www.cnblogs.com/LQBlog/p/9163424.html
Copyright © 2020-2023  润新知