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

 

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