netty系列之:使用UDP協議

flydean發表於2021-08-14

簡介

在之前的系列文章中,我們到了使用netty做聊天伺服器,聊天伺服器使用的SocketChannel,也就是說底層的協議使用的是Scoket。今天我們將會給大家介紹如何在netty中使用UDP協議。

UDP協議

UDP( User Datagram Protocol ),也叫使用者資料包協議。

UDP 的主要功能和亮點並不在於它引入了什麼特性,而在於它忽略的那些特性:不保證訊息交付,不保證交付順序,不跟蹤連線狀態,不需要擁塞控制。

我們來看一下UDP的資料包:

UDP是一種無連線的協議,傳送者只管傳送資料包即可,並不負責處理和保證資料是否成功傳送,資料是否被處理完成等。它的唯一作用就是傳送。

在JDK中表示UDP的有一個專門的類叫做:java.net.DatagramPacket,在NIO中還有一個java.nio.channels.DatagramChannel,專門負責處理UDP的channel。

這裡我們要將的是netty,netty中對於UDP協議也有上面的兩個類,名字雖然是一樣的,但是對應的包不同。他們分別是:

io.netty.channel.socket.DatagramPacket 和 io.netty.channel.socket.DatagramChannel,當然netty中的這兩個類是對JDK自帶類的增強。

先看一下netty中DatagramPacket的定義:

public class DatagramPacket
        extends DefaultAddressedEnvelope<ByteBuf, InetSocketAddress> implements ByteBufHolder 

DatagramPacket類實現了ByteBufHolder介面,表示它裡面存放的是ByteBuf。然後他又繼承自DefaultAddressedEnvelope,這個類是對地址的封裝,其中ByteBuf表示傳遞訊息的型別,InetSocketAddress表示目標的地址,它是一個IP地址+埠號的封裝。

從上面的UDP協議我們知道,UDP只需要知道目標地址和對應的訊息即可,所以DatagramPacket中包含的資料已經夠用了。

DatagramChannel是用來傳遞DatagramPacket的,因為DatagramChannel是一個介面,所以一般使用NioDatagramChannel作為真正使用的類。

String和ByteBuf的轉換

之前我們講到過,netty中的channel只接受ByteBuf資料型別,如果直接寫入String會報錯,之前的系列文章中,我們講過兩種處理方法,第一種是使用ObjectEncoder和ObjectDecoder在寫入ByteBuf之前,對物件進行序列化,這一種不僅適合String,也適合Object物件。

第二種是使用StringEncoder和StringDecoder專門處理String的encode和decode,這種處理只能處理String的轉換,對Object無效。

如果你不想使用這些encoder和decoder還可以直接使用ByteBuf和String進行轉換。

比如要將String寫入ByteBuf可以呼叫Unpooled.copiedBuffer的命令如下:

Unpooled.copiedBuffer("開始廣播", CharsetUtil.UTF_8)

將ByteBuf轉換成為String則可以呼叫:

byteBuf.toString(CharsetUtil.UTF_8)

構建DatagramPacket

DatagramPacket總共可以接受三個引數,分別是要傳送的資料data,要接收資料包的地址和要傳送資料包的地址。

這裡我們並不關心傳送資料包的地址,那麼只需要兩個引數即可,對於客戶端來說,我們傳送一個”開始廣播“的訊息給伺服器端,告訴伺服器端可以向客戶傳送回覆訊息了,如下所示:

new DatagramPacket(
                    Unpooled.copiedBuffer("開始廣播", CharsetUtil.UTF_8),
                    SocketUtils.socketAddress("255.255.255.255", PORT))

上我們使用SocketUtils.socketAddress建立了一個特殊的地址,255.255.255.255是一個特殊的廣播地址,意味著所有的主機,因為我們的客戶端並不知道伺服器的地址,所以使用255.255.255.255來廣播。

構建好的DatagramPacket,裡面有一個sender()方法,可以用來獲取客戶端的地址,所以在伺服器端可以這樣構建要傳送的packge:

new DatagramPacket(
                    Unpooled.copiedBuffer("廣播: " + nextQuote(), CharsetUtil.UTF_8), packet.sender())

啟動客戶端和伺服器

UDP的客戶端和伺服器啟動和socket稍微有所不同,如果是socket,那麼使用的channel是NioSocketChannel,如果是UDP,則使用的是NioDatagramChannel。如下是伺服器端啟動的程式碼:

EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioDatagramChannel.class)
             .option(ChannelOption.SO_BROADCAST, true)
             .handler(new UDPServerHandler());

            b.bind(PORT).sync().channel().closeFuture().await();
        } finally {
            group.shutdownGracefully();
        }

注意,這裡我們需要設定ChannelOption.SO_BROADCAST為true,因為UDP是以廣播的形式傳送訊息的。

客戶端的實現和socket稍微有所不同,下面是客戶端的啟動實現:

 EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioDatagramChannel.class)
             .option(ChannelOption.SO_BROADCAST, true)
             .handler(new UDPClientHandler());

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

對於UDP來說,並不存在地址繫結一說,所以上Bootstrap呼叫bind(0)。

總結

本文講解了netty中UDP協議的實現,UDP相較於Socket連線而言更加簡單。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/11-netty-udp/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章