簡介
在之前的系列文章中,我們到了使用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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!