Netty 广播机制-服务端-客户端的实现方式

需求分析

服务端IP地址发生变化后,仍然与客户端保持长链接

  1. 服务端IP地址发生变化后,通过广播机制发送ip地址。
  2. 客户端收到UDP的信息后,重新初始化TCP长链接。
  3. 客户端校验UDP信息是否重复,避免多次初始化。
  4. 验证跨网段的广播发送

逻辑过程

  • 初始化服务端的广播
  • 初始化客户端的广播
  • 局域网网段内发广播
  • 广播UDP信息协议处理

code 服务端

netty 用的4.0,因为在初始化的过程中 出现了.await()方法,这个方法在源码中是堵塞的,所以初始化的过程应该切换到子线程中,否则会触发ANR响应。引导者只能使用Bootstrap ,测试服务端的ServerBootstrap 失败。以下是初始化的基本配置,具体原理可查看w3c的教学内容 ,此处不做更多解释,只是方便你快速使用。

    /**
     * 广播初始化
     */
    public  void initBroadCast(NettyServerInterface serverInterface) {
        mUdpChannelHandler = new UDPChannelHandler(serverInterface);
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
                    Bootstrap bootstrap = new Bootstrap(); //是否可以和服务端 共用待确认ServerBootstrap
                    bootstrap.group(eventLoopGroup)
                            .channel(NioDatagramChannel.class)
                            .option(ChannelOption.SO_BROADCAST, true)
                            .handler(mUdpChannelHandler);
                    updChannel = bootstrap.bind(9999).sync().channel();
                    updChannel.closeFuture().await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();


    }

code客户端

客户端初始化和服务端基本一致,主要注意如何处理信息发送。

 private void initUDP() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    Bootstrap bootstrap;
                    EventLoopGroup group;
                    group = new NioEventLoopGroup();
                    bootstrap = new Bootstrap();
                    bootstrap.group(group)  //1
                            .channel(NioDatagramChannel.class)
                            .option(ChannelOption.SO_BROADCAST, true)
                            .handler(new UDPChannelHandler(CoreService.this));

                    Channel channel = bootstrap.bind(9999).sync().channel();
                    channel.closeFuture().await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();


    }

服务端发送

此处的udpChannel 就是初始化时生成的,广播发送的消息载体规定了用 DatagramPacket(注意导包非java 内的),InetSocketAddress 构造函数内可以注入广播发送的网络范围,255.255.255.255是同一网段的局域网内发送,9999指定端口号接收,即客户端监听也需要是这个端口。code 中加了前后缀就行数据包装!!IP@@。


    public static void sendUDP() {
        if (updChannel != null) {
            String ip = "!!" + localIP + "@@";
            ByteBuf byteBuf = Unpooled.copiedBuffer(ip, CharsetUtil.UTF_8);
            DatagramPacket packet = new DatagramPacket(byteBuf, new InetSocketAddress("255.255.255.255", 9999)); //跨网段发广播待验证。
            updChannel.writeAndFlush(packet);
            Log.i(TAG, "sendUDP: " + ip);
        }
    }

客户端接收

handler 是继承simpleChannelInboundHandler 改类内部有个方法channelRead0,可以提前实现泛型转换,因服务端发送的消息体是DatagramPacket,所以保持一致性,接收端没有做任何加密,解密的操作,直接泛型转换即可,如果你的channelRead0()不执行,很有可能是你导包导错了。

package com.nes.air.controlair.netty;

import android.util.Log;


import io.netty.channel.socket.DatagramPacket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * 广播 channel
 */
public class UDPChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    private static final String TAG = "UDPChannelHandler";
    private UDPInterface mUdpInterface;
    public UDPChannelHandler(UDPInterface udpInterface) {
        this.mUdpInterface=udpInterface;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        Log.i(TAG, "channelActive: ");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        Log.i(TAG, "channelInactive: ");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket packet) throws Exception {
        String data = packet.content().toString(CharsetUtil.UTF_8);
        Log.i(TAG, "channelRead0: "+data);
        mUdpInterface.channelRead0(data);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        Log.i(TAG, "exceptionCaught: ");
    }




}

希望对你有帮助!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Netty-Mqtt-Client实现Mqtt客户端发布消息和订阅消息的核心Java代码,带注释说明: ```java import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.codec.mqtt.MqttMessageBuilders.*; // 创建一个Mqtt客户端类 public class MqttClient { private final String clientId; // 客户端ID private final String serverHost; // 服务器主机名 private final int serverPort; // 服务器端口号 private final String username; // 用户名 private final String password; // 密码 private final int keepAlive; // 心跳间隔时间 private EventLoopGroup group; // Netty线程组 private MqttClientInitializer initializer; // Netty客户端初始化器 private Channel channel; // Netty通道 // 构造方法,初始化Mqtt客户端配置 public MqttClient(String clientId, String serverHost, int serverPort, String username, String password, int keepAlive) { this.clientId = clientId; this.serverHost = serverHost; this.serverPort = serverPort; this.username = username; this.password = password; this.keepAlive = keepAlive; } // 连接服务器 public void connect() { group = new NioEventLoopGroup(); // 创建Netty线程组 initializer = new MqttClientInitializer(clientId, username, password, keepAlive); // 创建Netty客户端初始化器 Bootstrap bootstrap = new Bootstrap(); // 创建Netty客户端启动器 bootstrap.group(group) .channel(NioSocketChannel.class) .remoteAddress(serverHost, serverPort) .handler(initializer); try { ChannelFuture future = bootstrap.connect().sync(); // 连接服务器,同步等待连接完成 if (future.isSuccess()) { // 连接成功 channel = future.channel(); // 获取Netty通道 } } catch (InterruptedException e) { e.printStackTrace(); } } // 断开连接 public void disconnect() { if (channel != null && channel.isActive()) { channel.close(); // 关闭Netty通道 } if (group != null) { group.shutdownGracefully(); // 关闭Netty线程组 } } // 发布消息 public void publish(String topic, String message, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, false, 0); MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, 0); ByteBuf payload = Unpooled.buffer(); payload.writeBytes(message.getBytes()); MqttPublishMessage publishMessage = new MqttPublishMessage(header, variableHeader, payload); channel.writeAndFlush(publishMessage); // 发送Mqtt PUBLISH消息 } // 订阅主题 public void subscribe(String topic, MqttQoS qos) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.SUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttTopicSubscription topicSubscription = new MqttTopicSubscription(topic, qos); MqttSubscribePayload payload = new MqttSubscribePayload(Arrays.asList(topicSubscription)); MqttSubscribeMessage subscribeMessage = new MqttSubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(subscribeMessage); // 发送Mqtt SUBSCRIBE消息 } // 取消订阅主题 public void unsubscribe(String topic) { MqttFixedHeader header = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0); MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(1); MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Arrays.asList(topic)); MqttUnsubscribeMessage unsubscribeMessage = new MqttUnsubscribeMessage(header, variableHeader, payload); channel.writeAndFlush(unsubscribeMessage); // 发送Mqtt UNSUBSCRIBE消息 } } ``` 以上代码中,我们创建了一个MqttClient类,该类通过Netty-Mqtt-Client实现了Mqtt客户端发布消息和订阅消息的功能。具体实现细节如下: - connect()方法:连接Mqtt服务器,其中我们通过Netty创建了一个NioEventLoopGroup线程组、一个MqttClientInitializer客户端初始化器和一个Bootstrap客户端启动器,并将它们配置好后发起连接请求; - disconnect()方法:断开Mqtt服务器连接,关闭Netty通道和线程组; - publish()方法:发布Mqtt消息,其中我们使用了MqttFixedHeader、MqttPublishVariableHeader、ByteBuf和MqttPublishMessage等Netty-Mqtt-Client提供的类来构建Mqtt PUBLISH消息,并通过Netty通道将其发送给服务器; - subscribe()方法:订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttTopicSubscription、MqttSubscribePayload和MqttSubscribeMessage等Netty-Mqtt-Client提供的类来构建Mqtt SUBSCRIBE消息,并通过Netty通道将其发送给服务器; - unsubscribe()方法:取消订阅Mqtt主题,其中我们使用了MqttFixedHeader、MqttMessageIdVariableHeader、MqttUnsubscribePayload和MqttUnsubscribeMessage等Netty-Mqtt-Client提供的类来构建Mqtt UNSUBSCRIBE消息,并通过Netty通道将其发送给服务器

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值