UDP是用戶數據報協議(User Datagram Protocol,UDP)的簡稱,其主要做用是將網絡數據流量壓縮成數據報形式,提供面向事務的簡單信息傳送服務。與TCP協議不一樣,UDP協議直接利用IP協議進行UDP數據報的傳輸,UDP提供的是面向無鏈接的、不可靠的數據報投遞服務。當使用UDP協議傳輸信息時,用戶應用程序必須負責解決數據報丟失、重複、排序,差錯確認等問題。因爲UDP具備資源消耗小、處理速度快的優勢,因此一般視頻、音頻等可靠性要求不高的數據傳輸通常會使用UDP,即使有必定的丟包率,也不會對功能形成嚴重的影響。java
UDP是無鏈接的,通訊雙方不須要創建物理鏈路鏈接。在網絡中它用於處理數據包,在OSI模型中,它處於第四層傳輸層,即位於IP協議的上一層。它不對數據報分組、組裝、校驗和排序,所以是不可靠的。報文的發送者不知道報文是否被對方正確接收。bootstrap
UDP數據報格式有首部和數據兩個部分,首部很簡單,爲8個字節,包括如下部分:安全
(1)源端口:源端口號,2個字節,最大值爲65535;網絡
(2)目的端口:目的端口號,2個字節,最大值爲65535;多線程
(3)長度:2字節,UDP用戶數據報的總長度;併發
(4)校驗和:2字節,用於校驗UDP數據報的數字段和包含UDP數據報首部的「僞首部」。其校驗方法相似於IP分組首部中的首部校驗和。dom
僞首部,又稱爲僞包頭(Pseudo Header):是指在TCP的分段或UDP的數據報格式中,在數據報首部前面增長源IP地址、目的IP地址、IP分組的協議字段、TCP或UDP數據報的總長度等,共12字節,所構成的擴展首部結構。此僞首部是一個臨時的結構,它既不向上也不向下傳遞,僅僅是爲了保證能夠校驗套接字的正確性。socket
UDP協議數據報格式示意圖如圖:ide
UDP協議的特色以下。oop
(1)UDP傳送數據前並不與對方創建鏈接,即UDP是無鏈接的。在傳輸數據前,發送方和接收方相互交換信息使雙方同步;
(2)UDP對接收到的數據報不發送確認信號,發送端不知道數據是否被正確接收,也不會重發數據;
(3)UDP傳送數據比TCP快速,系統開銷也少:UDP比較簡單,UDP頭包含了源端口、目的端口、消息長度和校驗和等不多的字節。因爲UDP比TCP簡單、靈活,經常使用於可靠性要求不高的數據傳輸,如視頻、圖片以及簡單文件傳輸系統(TFTP)等。TCP則適用於可靠性要求很高但實時性要求不高的應用,如文件傳輸協議FTP、超文本傳輸協議HTTP、簡單郵件傳輸協議SMTP等。
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; public class ChineseProverbServer { public void run(int port) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); //因爲使用UDP通訊,在建立Channel的時候須要經過NioDatagramChannel來建立 b.group(group).channel(NioDatagramChannel.class) //隨後設置Socket參數支持廣播, .option(ChannelOption.SO_BROADCAST, true) //最後設置業務處理handler。 //相比於TCP通訊,UDP不存在客戶端和服務端的實際鏈接, //所以不須要爲鏈接(ChannelPipeline)設置handler, //對於服務端,只須要設置啓動輔助類的handler便可。 .handler(new ChineseProverbServerHandler()); b.bind(port).sync().channel().closeFuture().await(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { e.printStackTrace(); } } new ChineseProverbServer().run(port); } } 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 io.netty.util.internal.ThreadLocalRandom; public class ChineseProverbServerHandler extends SimpleChannelInboundHandler { // 諺語列表 private static final String[] DICTIONARY = {"只要功夫深,鐵棒磨成針。", "舊時王謝堂前燕,飛入尋常百姓家。", "洛陽親友如相問,一片冰心在玉壺。", "一寸光陰一寸金,寸金難買寸光陰。", "老驥伏櫪,志在千里。烈士暮年,壯心不已!"}; private String nextQuote() { //因爲ChineseProverbServerHandler存在多線程併發操做的可能, //因此使用了Netty的線程安全隨機類ThreadLocalRandom。 // 若是使用的是JDK7,能夠直接使用JDK7的java.util.concurrent.ThreadLocalRandom。 int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length); return DICTIONARY[quoteId]; } @Override public void messageReceived(ChannelHandlerContext ctx, Object msg)throws Exception { //Netty對UDP進行了封裝,所以,接收到的是Netty封裝後的io.netty. channel.socket.DatagramPacket對象。 DatagramPacket packet = (DatagramPacket) msg; //將packet內容轉換爲字符串(利用ByteBuf的toString(Charset)方法), String req = packet.content().toString(CharsetUtil.UTF_8); System.out.println(req); // 而後對請求消息進行合法性判斷:若是是「諺語字典查詢?」,則構造應答消息返回。 // DatagramPacket有兩個參數:第一個是須要發送的內容,爲ByteBuf; // 另外一個是目的地址,包括IP和端口,能夠直接從發送的報文DatagramPacket中獲取。 if ("諺語字典查詢?".equals(req)) { ctx.writeAndFlush( new DatagramPacket(Unpooled.copiedBuffer("諺語查詢結果: " + nextQuote(), CharsetUtil.UTF_8), packet.sender())); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); cause.printStackTrace(); } }
UDP程序的客戶端和服務端代碼很是類似,惟一不一樣之處是UDP客戶端會主動構造請求消息,向本網段內的全部主機廣播請求消息,對於服務端而言,接收到廣播請求消息以後會向廣播消息的發起方進行定點發送。
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 java.net.InetSocketAddress; public class ChineseProverbClient { public void run(int port) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); //建立UDP Channel和設置支持廣播屬性等與服務端徹底一致。 // 因爲不須要和服務端創建鏈路,UDP Channel建立完成以後,客戶端就要主動發送廣播消息; // TCP客戶端是在客戶端和服務端鏈路創建成功以後由客戶端的業務handler發送消息,這就是二者最大的區別。 b.group(group).channel(NioDatagramChannel.class) .option(ChannelOption.SO_BROADCAST, true) .handler(new ChineseProverbClientHandler()); Channel ch = b.bind(0).sync().channel(); // 向網段內的全部機器廣播UDP消息 // 用於構造DatagramPacket發送廣播消息, // 注意,廣播消息的IP設置爲「255.255.255.255」。 // 消息廣播以後,客戶端等待15s用於接收服務端的應答消息,而後退出並釋放資源。 ch.writeAndFlush( new DatagramPacket(Unpooled.copiedBuffer("諺語字典查詢?",CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255", port)) ).sync(); if (!ch.closeFuture().await(15000)) { System.out.println("查詢超時!"); } } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { e.printStackTrace(); } } new ChineseProverbClient().run(port); } } import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import io.netty.util.CharsetUtil; public class ChineseProverbClientHandler extends SimpleChannelInboundHandler { @Override public void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception { //接收到服務端的消息以後將其轉成字符串,而後判斷是否以「諺語查詢結果:」開頭, //若是沒有發生丟包等問題,數據是完整的,就打印查詢結果,而後釋放資源。 DatagramPacket msg = (DatagramPacket)o; String response = msg.content().toString(CharsetUtil.UTF_8); if (response.startsWith("諺語查詢結果:")) { System.out.println(response); ctx.close(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }