Netty入門之UDP協議

在Java中由很多Socket套接字實現,如SocketImpl(對應TCP協議)、DatagramSocketImpl(對應UDP協議)

Netty一般使用的Channel都是NioSocketChannel,它是對應TCP協議的,如果要使用UDP協議,則需要使用另一種Channel,NioDatagramChannel

 

QuoteOfTheMomentClient

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.SocketUtils;
import lombok.extern.slf4j.Slf4j;


/**
 * 一個向 {@link QuoteOfTheMomentServer}詢問當下報價(QOTM)的UDP廣播客戶端
 */
@Slf4j
public final class QuoteOfTheMomentClient {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

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

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioDatagramChannel.class)//注意這裏使用的是NioDatagramChannel,而不是NioSocketChannel,底層網絡協議的切換在Channel層
                    .option(ChannelOption.SO_BROADCAST, true)// Socket參數,設置廣播模式。
                    .handler(new QuoteOfTheMomentClientHandler());

            Channel ch = b.bind(0).sync().channel();

            /* 廣播QOTM請求到端口,直接發送UDP數據報 */
            ch.writeAndFlush(new DatagramPacket(
                    Unpooled.copiedBuffer("QOTM?", CharsetUtil.UTF_8),
                    SocketUtils.socketAddress("255.255.255.255", PORT))).sync();

            /* 當收到響應時,QuoteOfTheMomentClientHandler將關閉DatagramChannel。如果在5秒鐘內未關閉通道,則打印錯誤消息並退出。 */
            if (!ch.closeFuture().await(5000)) {
               log.error("QOTM請求超時.");
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}

1.Bootstrap設置的Channel類爲NioDatagramChannel類,所以這裏使用的是UDP協議

可以看出Netty的底層網絡協議是使用Channel來區分的

2.設置了ChannelOption.SO_BROADCAST廣播模式,這個後面再說

3.writeAndFlush的參數爲DatagramPacket,即UDP數據報,這個不是JDK裏面的類,而是Netty自有的類,這個類需要數據報內容、地址作爲參數

而Netty裏面很多方法參數類型都是Object,也是爲了網絡協議切換後,載荷的切換方便

 

QuoteOfTheMomentClientHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

/* 響應爲UDP數據包,DatagramPacket */
@Slf4j
public class QuoteOfTheMomentClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        String response = msg.content().toString(CharsetUtil.UTF_8);
        if (response.startsWith("QOTM: ")) {
            log.info("Quote of the Moment: " + response);
            ctx.close();
        }
    }

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

1.繼承了SimpleChannelInboundHandler<DatagramPacket>類,泛型參數爲DatagramPacket,即載荷爲UDP數據報

2.實現的讀取方法爲channelRead0,這個方法的載荷參數爲DatagramPacket類型

 

QuoteOfTheMomentServer

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.slf4j.Slf4j;

/**
 * 一個UDP服務端,用於應答QOTM請求
 */
@Slf4j
public final class QuoteOfTheMomentServer {

    private static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioDatagramChannel.class)//也需要使用UDP的Channel
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new QuoteOfTheMomentServerHandler());
            b.bind(PORT).sync().channel().closeFuture().await();
        } finally {
            group.shutdownGracefully();
        }
    }
}

1.因爲這裏採用的是廣播模式,所以每個節點都是服務器/客戶端,這裏也是使用的Bootstrap,而不是ServerBootrap

2.這裏進行接收,也需要使用NioDatagramChannel

3.開啓廣播模式

 

QuoteOfTheMomentServerHandler

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j
public class QuoteOfTheMomentServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    private static final Random random = new Random();

    // Quotes from Mohandas K. Gandhi:
    private static final String[] quotes = {
            "Where there is love there is life.",
            "First they ignore you, then they laugh at you, then they fight you, then you win.",
            "Be the change you want to see in the world.",
            "The weak can never forgive. Forgiveness is the attribute of the strong.",
    };

    /* 獲取隨機的句子 */
    private static String nextQuote() {
        int quoteId;
        synchronized (random) {
            quoteId = random.nextInt(quotes.length);
        }
        return quotes[quoteId];
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        log.info(packet.content().toString(CharsetUtil.UTF_8));
        if ("QOTM?".equals(packet.content().toString(CharsetUtil.UTF_8))) {
            ctx.write(new DatagramPacket(Unpooled.copiedBuffer("QOTM: " + nextQuote(), CharsetUtil.UTF_8), packet.sender()));
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

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

1.這裏構造UDP數據報時,使用了發送者的地址,說明這個應答不是廣播

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