Android Netty客户端连接多个服务端IP、端口及重连机制

项目中需要客户端APP连接多个服务端IP和端口,需要与服务端保持长链接,说到长链接的技术首先想到Netty框架,也是目前使用最广泛的长链接框架;Netty适合用来开发高性能长连接的客户端或服务器,其次Netty基于NIO,并做了封装与优化以及一些高级算法,使得使用起来相比原生NIO更加容易,性能更好。

1、配置build.gradle文件

build.gradle文件的dependencies标签下添加Netty引用

implementation 'io.netty:netty-all:4.1.23.Final'

2、主要代码

2.1、调用连接
    private BootNettyClientThread bc;
    private BootNettyClientThread bc1;
    private BootNettyClientThread bc2;
    private BootNettyClientThread bc3;
    //创建连接线程
    private void connectSever() {
        bc = new BootNettyClientThread(Constants.PORT_1, Constants.IP_2, this);
        bc.start();
        bc1 = new BootNettyClientThread(Constants.PORT_2, Constants.IP, this);
        bc1.start();
        bc2 = new BootNettyClientThread(Constants.PORT_3, Constants.IP, this);
        bc2.start();
        bc3 = new BootNettyClientThread(Constants.PORT_4, Constants.IP, this);
        bc3.start();
    }

    //数据发送
    public void sendMsg(String string, int sendPort, String desc) {
        int channelSize = BootNettyClientChannelCache.channelMapCache.size();
        if (channelSize > 0) {
            BootNettyClientChannelCache.removeDuplicated2();
            for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
                BootNettyClientChannel bootNettyChannel = entry.getValue();
                int port = bootNettyChannel.getPort();
                if (port == sendPort) {
                    ByteBuf buf = Unpooled.buffer().writeBytes(HexUtils.hexStringToByte(string));
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    bootNettyChannel.getChannel().writeAndFlush(buf);
                    LogUtils.i("send port:" + port + ",desc:" + desc + ",data:" + string);
                }
            }
        }
    }
    
    @Override
    public void onChannelRead(int port, String data) {
        //接收数据
        switch (port) {
            case Constants.PORT_1:
                break;
            case Constants.PORT_2:
                break;
            case Constants.PORT_3:
                break;
            case Constants.PORT_4:
                break;
            default:
                break;
        }
    }

    @Override
    public void onConnectState(int state, int port) {
        //连接成功或断开
    }

    @Override
    public void onExDisConnect(int state) {
       //异常断开
    }

    @Override
    public void onConnectFail(String string) {
        //连接失败
    }
    
    //销毁时中断线程、断开连接、关闭服务等操作
    private void releaseThread() {
        if (bc != null) {
            bc.releaseNetty();
            bc.interrupt();
            bc = null;
        }
        if (bc1 != null) {
            bc1.releaseNetty();
            bc1.interrupt();
            bc1 = null;
        }
        if (bc2 != null) {
            bc2.releaseNetty();
            bc2.interrupt();
            bc2 = null;
        }
        if (bc3 != null) {
            bc3.releaseNetty();
            bc3.interrupt();
            bc3 = null;
        }
    }
2.2、连接线程
public class BootNettyClientThread extends Thread {

    private final int port;
    private final String address;
    private IMessageListener mIMessageListener;
    private BootNettyClient mBootNettyClient;

    public BootNettyClientThread(int port, String address,
                                 IMessageListener iMessageListener) {
        this.port = port;
        this.address = address;
        this.mIMessageListener = iMessageListener;
        mBootNettyClient = new BootNettyClient(mIMessageListener);
    }

    public void run() {
        try {
            mBootNettyClient.connect(address, port);
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        }
    }

    public void releaseNetty() {
        if (mBootNettyClient != null) {
            mBootNettyClient.stop();
        }
    }
2.3、建立连接
class BootNettyClient {

    private Bootstrap b = new Bootstrap();
    private EventLoopGroup group;
    private IMessageListener mIMessageListener;

    BootNettyClient(IMessageListener iMessageListener) {
        mIMessageListener = iMessageListener;
        group = new NioEventLoopGroup();
        b.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new BootNettyChannelInitializer(mIMessageListener));
    }

    void connect(String host, int port) {
        LogUtils.i("connect " + host + " " + Thread.currentThread());
        b.connect(host, port).addListener((ChannelFutureListener) future -> {
            /*
             * 这里就不是主线程了,这里是 netty 线程中执行
             */
            if (future.isSuccess()) {
                BootNettyClientChannel bootNettyClientChannel = new BootNettyClientChannel();
                Channel channel = future.channel();
                String id = channel.id().toString();
                bootNettyClientChannel.setPort(port);
                bootNettyClientChannel.setChannel(channel);
                bootNettyClientChannel.setCode("clientId:" + id);
                LogUtils.i("channel:" + channel.isActive());
                BootNettyClientChannelCache.save("clientId:" + id, bootNettyClientChannel);
                LogUtils.i("netty client start success=" + id);
                LogUtils.i("connect success " + host + " " + Thread.currentThread());
            } else {
                mIMessageListener.onConnectFail("WIFI连接失败");
                LogUtils.i("connect failed " + host + " " + Thread.currentThread());
                // 连接不成功,5秒后重新连接
                future.channel().eventLoop().schedule(() -> {
                    LogUtils.i("reconnect " + host + " " + Thread.currentThread());
                    connect(host, port);
                }, 5, TimeUnit.SECONDS);
            }
        });
    }

    void stop() {
        //关闭服务线程
        if (group != null) {
            group.shutdownGracefully();
            group = null;
        }
        //循环channel,然后断开连接,如果不断开则会创建多个新连接
        for (Map.Entry<String, BootNettyClientChannel> entry : BootNettyClientChannelCache.channelMapCache.entrySet()) {
            BootNettyClientChannel bootNettyChannel = entry.getValue();
            bootNettyChannel.getChannel().disconnect();
        }
        BootNettyClientChannelCache.clear();
    }
2.4、数据接收、连接状态、重连操作
public class BootNettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter {

    private IMessageListener messageListener;

    BootNettyChannelInboundHandlerAdapter(IMessageListener messageListener) {
        this.messageListener = messageListener;
    }

    /**
     * 从服务端收到新的数据时,这个方法会在收到消息时被调用
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg == null) {
            return;
        }
        //BootNettyClientChannel bootNettyClientChannel = BootNettyClientChannelCache.get("clientId:" + ctx.channel().id().toString());
        //if (bootNettyClientChannel != null) {
        //bootNettyClientChannel.setLastData((String) msg);
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        messageListener.onChannelRead(inSocket.getPort(), (String) msg);
        //}
        //回应服务端
        //ctx.write("I got server message thanks server!");
    }

    /**
     * 从服务端收到新的数据、读取完成时调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        LogUtils.i("channelReadComplete");
        ctx.flush();
    }

    /**
     * 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();//抛出异常,断开与客户端的连接
        messageListener.onExDisConnect(1);
        BootNettyClientChannelCache.clear();
    }

    /**
     * 客户端与服务端第一次建立连接时 执行
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        LogUtils.i("连接成功:" + clientIp + ":" + inSocket.getPort());
        messageListener.onConnectState(0, inSocket.getPort());
    }

    /**
     * 客户端与服务端 断连时 执行
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        InetSocketAddress inSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = inSocket.getAddress().getHostAddress();
        int port = inSocket.getPort();
        ctx.close(); //断开连接时,必须关闭,否则造成资源浪费
        LogUtils.i("channelInactive异常断开,重新连接:" + clientIp + ":" + port);
        messageListener.onConnectState(1, port);
        BootNettyClientChannelCache.remove("clientId:" + ctx.channel().id().toString());
        SystemClock.sleep(1000);
        //如果退出界面不走重连操作,可设置Application中定义全局标志位
        if (!MyApplication.isFinish) {
            BootNettyClientThread thread = new BootNettyClientThread(port, clientIp, messageListener);
            thread.start();
        }
    }
2.5、定义收发编解码方式
public class BootNettyChannelInitializer extends ChannelInitializer<Channel> {

    private IMessageListener messageListener;

    BootNettyChannelInitializer(IMessageListener messageListener) {
        this.messageListener = messageListener;
    }

    @Override
    protected void initChannel(Channel ch) {
        ch.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        ch.pipeline().addLast("decoder", new MyDecoder());
        /**
         * 自定义ChannelInboundHandlerAdapter
         */
        ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter(messageListener));
    }
}
public class MyDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        //创建字节数组,buffer.readableBytes可读字节长度
        byte[] b = new byte[buffer.readableBytes()];
        //复制内容到字节数组b
        buffer.readBytes(b);
        out.add(HexUtils.byte2HexStrNoSpace(b));
    }
}
2.6、集合缓存channel
public class BootNettyClientChannel {

    private transient volatile Channel channel;
    //连接客户端唯一的code
    private String code;
    //客户端最新发送的消息内容
    private String lastData;
    private int port;

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getLastData() {
        return lastData;
    }

    public void setLastData(String lastData) {
        this.lastData = lastData;
    }

    public int getPort() {
        return port;
    }

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

    public static volatile Map<String, BootNettyClientChannel> channelMapCache = new ConcurrentHashMap<String, BootNettyClientChannel>();

    public static void add(String code, BootNettyClientChannel channel) {
        channelMapCache.put(code, channel);
    }

    public static BootNettyClientChannel get(String code) {
        return channelMapCache.get(code);
    }

    public static void remove(String code) {
        channelMapCache.remove(code);
    }

    public static void clear() {
        channelMapCache.clear();
    }

    public static void save(String code, BootNettyClientChannel channel) {
        if (channelMapCache.get(code) == null) {
            add(code, channel);
        }
    }

    public static void removeDuplicated2() {
        Set<Integer> set = new HashSet<>();
        Iterator<Map.Entry<String, BootNettyClientChannel>> iterator = channelMapCache.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, BootNettyClientChannel> entry = iterator.next();
            if (!set.add(entry.getValue().getPort())) {
                iterator.remove();
            }
        }
        LogUtils.i("channelMapCache.size:" + channelMapCache.size());
    }
}

以上便可完成客户端连接多个服务端IP和端口的需求,实现在不同端口接收和发送数据,目前在项目中使用不高,并没有做太深的优化,后续可以结合实际的业务需求深度code。

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值