簡介
在之前的系列文章中,我們到了使用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