6、netty实现udp单播和广播

注:源代码来自享学课堂,略有修改,学习之后所做笔记,方便回顾,也给大家一个参考 

目录

单播

回应端ansoweHandler

 回应端主函数

 发送端

 发送端主函数

执行结果 

广播:模拟日志发送

日志发送对象

 日志发送工具

 广播主函数

 广播主函数编码

 广播接收者

广播接受者handler 

 接受者解码

结果


单播

单播没有服务端和客户端概念

回应端ansoweHandler

和平常的handler没两样,只是writeAndFlush的时候,不是byteBuf,而是DatagramPacket了

public class MyAnswerHandler extends
        SimpleChannelInboundHandler<DatagramPacket> {

    /**
     * 应答的具体内容从常量字符串数组中取得,由nextQuote方法随机获取
     */
    private static final String[] DICTIONARY = {
            "只要功夫深,铁棒磨成针。",
            "旧时王谢堂前燕,飞入寻常百姓家。",
            "洛阳亲友如相问,一片冰心在玉壶。",
            "一寸光阴一寸金,寸金难买寸光阴。",
            "老骥伏枥,志在千里,烈士暮年,壮心不已"};
    private static Random r = new Random();

    private String nextQuote() {
        return DICTIONARY[r.nextInt(DICTIONARY.length - 1)];
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx,
                                DatagramPacket packet)
            throws Exception {

        //获得请求
        String req = packet.content().toString(CharsetUtil.UTF_8);
        System.out.println(req);
        if (MyUdpQuestion.QUESTION.equals(req)) {
            /**
             * 重新 new一个DatagramPacket对象,我们通过packet.sender()
             * 来获取发送者的地址。
             * 重新发送出去!
             */
            ctx.writeAndFlush(
                    new DatagramPacket(
                            Unpooled.copiedBuffer(
                                    MyUdpAnswer.ANSWER + nextQuote(),
                                    CharsetUtil.UTF_8),
                            packet.sender()));
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

 回应端主函数

只是channel变成了.channel(NioDatagramChannel.class),其他没有变化

public class MyUdpAnswer {
    public final static String ANSWER = "古诗来了:";

    public void run(int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /*和tcp的不同,udp没有接受连接的说法,所以即使是接收端,也使用Bootstrap*/
            Bootstrap b = new Bootstrap();
            /*由于我们用的是UDP协议,所以要用NioDatagramChannel来创建*/
            b.group(group)
                    .channel(NioDatagramChannel.class)
                    .handler(new MyAnswerHandler());
            //没有接受客户端连接的过程,监听本地端口即可
            ChannelFuture f = b.bind(port).sync();
            System.out.println("应答服务已启动.....");
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new MyUdpAnswer().run(port);
    }
}

 发送端

读写数据都是使用DatagramPacket 

public class MyQuestionHandler extends
        SimpleChannelInboundHandler<DatagramPacket> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
            throws Exception {
        //获得应答,DatagramPacket提供了content()方法取得报文的实际内容
        String response = msg.content().toString(CharsetUtil.UTF_8);
        if (response.startsWith(MyUdpAnswer.ANSWER)) {
            System.out.println(response);
            ctx.close();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //将UDP请求的报文以DatagramPacket打包发送给接受端
        ctx.writeAndFlush(new DatagramPacket(
                Unpooled.copiedBuffer("告诉我一句古诗", CharsetUtil.UTF_8),
                new InetSocketAddress("127.0.0.1", 8080)
        ));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 发送端主函数

ublic class MyUdpQuestion {
    public final static String QUESTION = "告诉我一句古诗";

    public void run(int port) throws Exception {

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    /*由于我们用的是UDP协议,所以要用NioDatagramChannel来创建*/
                    .channel(NioDatagramChannel.class)
                    .handler(new MyQuestionHandler());
            //不需要建立连接
            Channel ch = b.bind(0).sync().channel();

            //不知道接收端能否收到报文,也不知道能否收到接收端的应答报文
            // 所以等待15秒后,不再等待,关闭通信
            if (!ch.closeFuture().await(15000)) {
                System.out.println("查询超时!");
            }
        } catch (Exception e) {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int answerPort = 8080;

        new MyUdpQuestion().run(answerPort);
    }
}

执行结果 

服务端

应答服务已启动.....
告诉我一句古诗

发送端

古诗来了:旧时王谢堂前燕,飞入寻常百姓家。

广播:模拟日志发送:

广播端:

.option(ChannelOption.SO_BROADCAST,true)

接收端:

//设置套接字选项 SO_BROADCAST
.option(ChannelOption.SO_BROADCAST, true)
//允许端口重用
.option(ChannelOption.SO_REUSEADDR,true)

 

日志发送对象

@Data
public class LogMsg {
    public static final byte SEPARATOR = (byte) ':';
    /**
     * 源的 InetSocketAddress
     */
    private final InetSocketAddress source;
    /**
     * 消息内容
     */
    private final String msg;
    /**
     * 消息id
     */
    private final long msgId;
    /**
     * 消息发送或者接受的时间
     */
    private final long time;

    /**
     * 用于传入消息的构造函数
     */
    public LogMsg(String msg) {
        this(null, msg, -1, System.currentTimeMillis());
    }

    /**
     * 用于传出消息的构造函数
     */
    public LogMsg(InetSocketAddress source, long msgId,
                  String msg) {
        this(source, msg, msgId, System.currentTimeMillis());
    }

    public LogMsg(InetSocketAddress source, String msg, long msgId, long time) {
        this.source = source;
        this.msg = msg;
        this.msgId = msgId;
        this.time = time;
    }
}

 日志发送工具

public class LogConst {
    public final static int MONITOR_SIDE_PORT = 9998;
    private static final String[] LOG_INFOS = {
            "20180912:mark-machine:Send sms to 10001",
            "20180912:lison-machine:Send email to james@enjoyedu",
            "20180912:james-machine:Happen Exception",
            "20180912:peter-machine:人生不能象做菜,把所有的料都准备好了才下锅",
            "20180912:deer-machine:牵着你的手,就象左手牵右手没感觉,但砍下去也会痛!",
            "20180912:king-machine:我听别人说这世界上有一种鸟是没有脚的," +
                    "它只能一直飞呀飞呀,飞累了就在风里面睡觉,这种鸟一辈子只能下地一次," +
                    "那一次就是它死亡的时候.",
            "20180912:mark-machine:多年以后我有个绰号叫西毒,任何人都可以变得狠毒," +
                    "只要你尝试过什么叫妒嫉.我不介意其他人怎么看我," +
                    "我只不过不想别人比我更开心.我以为有一些人永远不会妒嫉," +
                    "因为他太骄傲 . 在我出道的时候,我认识了一个人," +
                    "因为他喜欢在东边出没,所以很多年以后,他有个绰号叫东邪.",
            "20180912:lison-machine:做人如果没有梦想,那和咸鱼有什么区别",
            "20180912:james-machine:恐惧让你沦为囚犯,希望让你重获自由," +
                    "坚强的人只能救赎自己,伟大的人才能拯救别人." +
                    "记着,希望是件好东西,而且从没有一样好东西会消逝." +
                    "忙活,或者等死.",
            "20180912:peter-machine:世界上最远的距离不是生和死," +
                    "而是我站在你的面前却不能说:我爱你",
            "20180912:deer-machine:成功的含义不在于得到什么," +
                    "而是在于你从那个奋斗的起点走了多远.",
            "20180912:king-machine:一个人杀了一个人,他是杀人犯.是坏人," +
                    "当一个人杀了成千上万人后,他是英雄,是大好人",
            "20180912:mark-machine:世界在我掌握中,我却掌握不住对你的感情",
            "20180912:lison-machine:我害怕前面的路,但是一想到你,就有能力向前走了。",
            "20180912:james-machine:早就劝你别吸烟,可是烟雾中的你是那么的美," +
                    "叫我怎么劝得下口。",
            "20180912:peter-machine:如果你只做自己能力范围之内的事情,就永远无法进步。" +
                    "昨天已成为历史,明天是未知的,而今天是上天赐予我们的礼物," +
                    "这就是为什么我们把它叫做现在!",
            "20180912:deer-machine:年轻的时候有贼心没贼胆,等到了老了吧," +
                    "贼心贼胆都有了,可贼又没了。",
            "20180912:king-machine:别看现在闹得欢,小心将来拉清单。"};

    private final static Random r = new Random();
    public static String getLogInfo(){
        return LOG_INFOS[r.nextInt(LOG_INFOS.length-1)];
    }
}

 广播主函数

public class LogEventBroadcaster {
    private final EventLoopGroup group;
    private final Bootstrap bootstrap;

    public LogEventBroadcaster(InetSocketAddress remoteAddress) {
        group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        //引导该 NioDatagramChannel(无连接的)
        bootstrap.group(group).channel(NioDatagramChannel.class)
                //设置 SO_BROADCAST 套接字选项
                .option(ChannelOption.SO_BROADCAST,true)
                .handler(new LogEventEncoder(remoteAddress));
    }

    public void run() throws Exception {
        //绑定 Channel
        Channel ch = bootstrap.bind(0).sync().channel();
        long count = 0;
        //启动主处理循环,模拟日志发送
        for (;;) {
            ch.writeAndFlush(new LogMsg(null, ++count,
                    LogConst.getLogInfo()));
            try {
                //休眠 2 秒,如果被中断,则退出循环;
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.interrupted();
                break;
            }
        }
    }

    public void stop() {
        group.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {

        //创建并启动一个新的 UdpQuestionSide 的实例
        LogEventBroadcaster broadcaster = new LogEventBroadcaster(
                //表明本应用发送的报文并没有一个确定的目的地,也就是进行广播
                new InetSocketAddress("255.255.255.255",
                        LogConst.MONITOR_SIDE_PORT));
        try {
            broadcaster.run();
        }
        finally {
            broadcaster.stop();
        }
    }
}

 广播主函数编码

public class LogEventEncoder extends MessageToMessageEncoder<LogMsg> {
    private final InetSocketAddress remoteAddress;

    /**
     * LogEventEncoder 创建了即将被发送到指定的 InetSocketAddress的 DatagramPacket 消息
     */
    public LogEventEncoder(InetSocketAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,
                          LogMsg logMsg, List<Object> out) throws Exception {
        byte[] msg = logMsg.getMsg().getBytes(CharsetUtil.UTF_8);
        //容量的计算:两个long型+消息的内容+分割符
        ByteBuf buf = channelHandlerContext.alloc()
                .buffer(8*2 + msg.length + 1);
        //将发送时间写入到 ByteBuf中
        buf.writeLong(logMsg.getTime());
        //将消息id写入到 ByteBuf中
        buf.writeLong(logMsg.getMsgId());
        //添加一个 SEPARATOR
        buf.writeByte(LogMsg.SEPARATOR);
        //将日志消息写入 ByteBuf中
        buf.writeBytes(msg);
        //将一个拥有数据和目的地地址的新 DatagramPacket 添加到出站的消息列表中
        out.add(new DatagramPacket(buf, remoteAddress));
    }
}

 广播接收者

public class LogEventMonitor {
    private final EventLoopGroup group;
    private final Bootstrap bootstrap;

    public LogEventMonitor(InetSocketAddress address) {
        group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        //引导该 NioDatagramChannel
        bootstrap.group(group)
                .channel(NioDatagramChannel.class)
                //设置套接字选项 SO_BROADCAST
                .option(ChannelOption.SO_BROADCAST, true)
                //允许端口重用
                .option(ChannelOption.SO_REUSEADDR,true)
                .handler( new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel)
                            throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        pipeline.addLast(new LogEventDecoder());
                        pipeline.addLast(new LogEventHandler());
                    }
                } )
                .localAddress(address);
    }

    public Channel bind() {
        //绑定 Channel。注意,DatagramChannel 是无连接的
        return bootstrap.bind().syncUninterruptibly().channel();
    }

    public void stop() {
        group.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        //构造一个新的 UdpAnswerSide并指明监听端口
        LogEventMonitor monitor = new LogEventMonitor(
                new InetSocketAddress(LogConst.MONITOR_SIDE_PORT));
        try {
            //绑定本地监听端口
            Channel channel = monitor.bind();
            System.out.println("UdpAnswerSide running");
            channel.closeFuture().sync();
        } finally {
            monitor.stop();
        }
    }
}

广播接受者handler 

public class LogEventHandler
        extends SimpleChannelInboundHandler<LogMsg> {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) throws Exception {
        //当异常发生时,打印栈跟踪信息,并关闭对应的 Channel
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx,
                             LogMsg event) throws Exception {
        //创建 StringBuilder,并且构建输出的字符串
        StringBuilder builder = new StringBuilder();
        builder.append(event.getTime());
        builder.append(" [");
        builder.append(event.getSource().toString());
        builder.append("] :[");
        builder.append(event.getMsgId());
        builder.append("] :");
        builder.append(event.getMsg());
        //打印 LogMsg 的数据
        System.out.println(builder.toString());
    }
}

 接受者解码

public class LogEventDecoder extends MessageToMessageDecoder<DatagramPacket> {

    @Override
    protected void decode(ChannelHandlerContext ctx,
                          DatagramPacket datagramPacket, List<Object> out)
            throws Exception {
        //获取对 DatagramPacket 中的数据(ByteBuf)的引用
        ByteBuf data = datagramPacket.content();
        //获得发送时间
        long sendTime = data.readLong();
        System.out.println("接受到"+sendTime+"发送的消息");
        //获得消息的id
        long msgId = data.readLong();
        //获得分隔符SEPARATOR
        byte sepa = data.readByte();
        //获取读索引的当前位置,就是分隔符的索引+1
        int idx = data.readerIndex();
        //提取日志消息,从读索引开始,到最后为日志的信息
        String sendMsg = data.slice(idx ,
                data.readableBytes()).toString(CharsetUtil.UTF_8);
        //构建一个新的 LogMsg 对象,并且将它添加到(已经解码的消息的)列表中
        LogMsg event = new LogMsg(datagramPacket.sender(),
                msgId, sendMsg);
        //作为本handler的处理结果,交给后面的handler进行处理
        out.add(event);
    }
}

结果

接受者接收到的日志

接受到1590052211879发送的消息
1590052211962 [/172.17.4.102:61921] :[1] :20180912:lison-machine:做人如果没有梦想,那和咸鱼有什么区别
接受到1590052213884发送的消息
1590052213884 [/172.17.4.102:61921] :[2] :20180912:lison-machine:Send email to james@enjoyedu

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章