天天看點

netty系列之:使用UDP協定

簡介

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

UDP協定

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

UDP 的主要功能和亮點并不在于它引入了什麼特性,而在于它忽略的那些特性:不保證消息傳遞,不保證傳遞順序,不跟蹤連接配接狀态,不需要擁塞控制。

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

netty系列之:使用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