netty搭建socket客户端,断线重连,发送数据,接收服务端数据,spring注入service

netty版本

    <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.31.Final</version>
        </dependency>

本代码主要包含4个类:

1.ClientCache存储建立的连接;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ClientCache {

    public static ConcurrentMap<String ,NioClient> clientMap=new  ConcurrentHashMap<String,NioClient>();
}

2.NioClient 建立连接,断线重连;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class NioClient implements  Runnable{

    private  final  static  Logger logger = LogManager.getLogger(NioClient.class);

    private String host;
    private int port;

    private SocketChannel socketChannel;
    private  EventLoopGroup group;


   private volatile boolean isChannelPrepared =false;
    private volatile boolean isClose=false;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public NioClient(String host ,int port){
        this.host=host;
        this.port=port;
    }


    public SocketChannel getSocketChannel() {
        return socketChannel;
    }

    public EventLoopGroup getGroup() {
        return group;
    }

    public  void connect(String host, Integer port) throws Exception {
       // synchronized (NioClient.class){
            if (!isChannelPrepared) {
                //配置服务端的NIO线程组
                group = new NioEventLoopGroup();
                try {
                    Bootstrap b = new Bootstrap();
                    b.group(group) // 绑定线程池
                            .channel(NioSocketChannel.class)
                            //.option(ChannelOption.SO_BACKLOG, 1024)
                            //.option(ChannelOption.SO_KEEPALIVE, true) // 2小时无数据激活心跳机制
                            .option(ChannelOption.TCP_NODELAY, true)
                            .handler(new ClientChannelInitializer());
                    ChannelFuture f = b.connect(host, port).sync();
                    f.addListener(new GenericFutureListener<Future<? super Void>>() {
                        @Override
                        public void operationComplete(Future<? super Void> future) {
                            if (future.isSuccess()) {
                                isChannelPrepared = true;
                                socketChannel = (SocketChannel) f.channel();
                                logger.info("与服务器{}:{}连接建立成功...", host, port);
                            } else {
                                isChannelPrepared = false;
                                logger.error("与服务器{}:{}连接建立失败...", host, port);
                                //  throw new NullPointerException();
                            }
                        }
                    });
                    // 关闭服务器通道
                    f.channel().closeFuture().sync();
                } catch (Exception e) {
                    isChannelPrepared = false;
                    //  e.printStackTrace();
                    logger.error("与服务器{}:{}连接出现异常...", host, port);
                } finally {
                    isChannelPrepared=false;
                    group.shutdownGracefully();
                    if (!isClose) {
                     logger.info("与服务器{}:{}连接已断开, {}秒后重连...", host, port,60);
                     }
                
                }
            }
       // }
    }


   /**
     * 断线重连
     */

    public void reConnect() {
        reConnect(host, port);
    }
   
    private void reConnect(String host, int port) {

        try {
            isClose=false;
            connect(host, port);
        } catch (Exception e) {
            isChannelPrepared = false;
            log.error(e.getMessage(), e);
        }
    }

    public   void  restart(){
        logger.info("客户端重启与服务器{}:{}的连接...", host, port);
        isChannelPrepared=false;
        isClose=false;
        group.shutdownGracefully();
        reConnect() 
        System.out.println("重启!");
    }

    public   void close(){
        logger.info("客户端关闭与服务器{}:{}的连接...", host, port);
        isChannelPrepared=false;
        this.isClose=true;
        
        for (String key :ClientCache.clientMap.keySet()) {
            if (ClientCache.clientMap.get(key).getSocketChannel()==socketChannel) {
                ClientCache.clientMap.remove(key);
            }
        }
        group.shutdownGracefully();
        group=null;
        socketChannel=null;
        logger.info("客户端关闭与服务器{}:{}的连接!", host, port);
    }

    public  boolean writeAndFlush(String data){
        logger.trace("向服务器{}:{}发送数据:{}", host, port,data);
        boolean b = false;
        if( isChannelPrepared) {
                socketChannel.writeAndFlush(data);
                b = true;
        }else{
            logger.error("与服务器{}:{}连接断开,无法发送数据[{}]...", host, port,data);
         //   throw  new RuntimeException();
        }
        return b;
    }

    @Override
    public void run() {
        try {
            this.isClose=false;
            connect(host,port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        NioClient nc=      new NioClient("127.0.0.1",9000);
        Thread t=      new Thread(nc);
        t.start();
        int j=0;
        while(j<5){
            j++;
            System.out.println(j);
            if(j%2==0){
                nc.writeAndFlush("test"+j+"\r\n");

            }
        }
        nc.close();
    }

}

3.ClientChannelInitializer 通道初始化配置;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) {
        ChannelPipeline pipeline = socketChannel.pipeline();

          /* LineBasedFrameDecoder的工作原理是:依次遍历ByteBuf中的可读字节,
        判断看其是否有”\n” 或 “\r\n”, 如果有就以此位置为结束位置。
        从可读索引到结束位置的区间的字节就组成了一行。 它是以换行符为结束标志的解码器,
        支持携带结束符和不带结束符两种解码方式,同时支持配置单行的最大长度,
        如果读到了最大长度之后仍然没有发现换行符,则抛出异常,同时忽略掉之前读到的异常码流。*/
        pipeline.addLast(new LineBasedFrameDecoder(10010));
        //字符串解码和编码
        //LineBasedFrameDecoder + StringDecoder 就是一个按行切换的文本解码器。
        pipeline.addLast( new StringDecoder());
        pipeline.addLast( new StringEncoder());
        //服务器的逻辑
        pipeline.addLast("handler", new NioClientHandler());
    }
}

4.NioClientHandler 接收到服务端数据处理,spring注入service;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class NioClientHandler extends SimpleChannelInboundHandler<Object> {

    private static  final Logger logger = LogManager.getLogger(NioClientHandler.class);

    private static NioClientHandler nioClientHandler;

    @Autowired
    private DataService dataService;  //你自己service

    public NioClientHandler() { }

    @PostConstruct
    public void init() {
        nioClientHandler = this;
        nioClientHandler.dataService = this.dataService;
    }

    /**
     *客户端接收消息
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //接收到服务端发来的数据,业务处理
                }}) .start();
    }


    /*
     * 读取完毕 服务端发送过来的数据之后的操作
     */
 /*   @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("服务端接收数据完毕..");
        //  ctx.channel().write("");
        //  ctx.channel().flush();
    }*/

    /**
     * 连接关闭!
     * */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        logger.error(" {}连接关闭!",ctx.channel().localAddress().toString() );
        super.channelInactive(ctx);
        SocketChannel channel= (SocketChannel) ctx.pipeline().channel();
        for (String key :ClientCache.clientMap.keySet()) {
            if (ClientCache.clientMap.get(key).getSocketChannel()==channel) {
                ClientCache.clientMap.get(key).isChannelPrepared=false;
            }
        }
        ctx.close();
      //  removeChannnelMap(ctx);
        // 关闭流

    }

    /**
     * 客户端主动连接服务端
     * */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
       // logger.info("RemoteAddress"+ ctx.channel().remoteAddress() + " active !");
        // logger.info("msg"+ ctx.m + " active !");
        //  ctx.writeAndFlush("连接成功!");
        super.channelActive(ctx);
    }

    /**
     * 发生异常处理
     * */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
      //  logger.error("连接异常,连接异常:"+ DateUtils.dateToString(new Date())+cause.getMessage(), cause);
        ctx.fireExceptionCaught(cause);
        //   removeChannnelMap(ctx);
        ctx.close();
    }

/*    *//**
     *删除map中ChannelHandlerContext
     *  *//*
    private void removeChannnelMap(ChannelHandlerContext ctx){
      *//*  for( String key :NioServer.map.keySet()){
            if( NioServer.map.get(key)!=null &&  NioServer.map.get(key).equals( ctx)){
                NioServer.map.remove(key);
            }
        }*//*
    }*/

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE))
                ctx.close();
            //标志该链接已经close 了 
        }
    }

    public static void main(String[] args) {


    }

}

6.启动netty客户端

  NioClient nc = new NioClient("127.0.0.1", "8000");
           new Thread(nc).start();
  ClientCache.clientMap.put("id", nc);

7.启动定时任务每分钟检查连接是否正常(定时器请自己配置或实现)

 @Scheduled(cron = "00 */01 * * * ?") // 每分钟执行一次
    public void nioTask() {
        new Thread(()->{
                try {
                    System.out.println("----------断线重连定时任务str-----------");
                    for (String key : ClientCache.clientMap.keySet()) {
                        if (!ClientCache.clientMap.get(key).isChannelPrepared && 
                                         ! ClientCache.clientMap.get(key).isClose) {
                            ClientCache.clientMap.get(key).reConnect();
                        }
                    };
                    System.out.println("===========断线重连定时任务end=========");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
    }

8.发送数据

 ClientCache.clientMap.get("id").writeAndFlush("id=123456;value=123\r\n");

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值