今天的主角是 UDP(User Datagram Protocol,用戶數據報協議)。
咱們都知道 TCP 是一種可靠的協議 —— 首先客戶端和服務端須要創建鏈接(三次「握手」),數據發送完畢須要斷開鏈接(四次「揮手」);若是發送數據時數據損壞或者丟失,那麼 TCP 會從新發送。保證可靠的代價就是效率的下降(創建鏈接和斷開鏈接就須要時間,保證數據的可靠性也須要額外的消耗)。與 TCP 相對應,UDP 是面向無鏈接的協議,而且它不保證數據是否會到達,也不保證到達的數據是否準確和數據順序是否正確 —— 因此相比於 TCP, UDP 的速度很快。在 須要不創建鏈接便可發送數據 的系統,或者 保證最快的傳輸速度比每一位數據都正確更重要 的系統(如視頻會議,丟失某個數據包只是一個畫面或者聲音的小干擾)中,UDP 纔是正確的選擇。實際上,在同一個網段,或者在信號很好的局域網,UDP 是很是可靠的。java
在 Java 中使用 UDP 時關鍵的兩個類分別是:DatagramSocket 和 DatagramPacket。
DatagramPacket 表示數據包,而 DatagrameSocket 用來發送和接收數據包。數組
由於 UDP 協議並不須要創建鏈接,因此咱們將數據(byte數組)放入 DatagramPacket 以後,還須要將目的地(IP地址和端口)放入到 DatagramPacket 中 —— DatagramSocket 的 send(DatagramPacket packet) 方法根據 packet 中指定目的地,將其包含的數據往這個目的地發送。至於數據是否能(準確)到達目的地,DatagramSocket 並不關心。socket
DatagramSocket 在接收數據包時,咱們須要爲其指定一個監聽的端口。當有包含了接收端機器 IP 地址和 DatagramSocket 所監聽端口的數據包到達時,DatagramSocket 的 receive(DatagramPacket packet) 方法便會對數據包進行接收,並將接收到的數據包填入到 packet 中。阿里雲
如今讓咱們來實踐 DatagramSocket 和 DatagramPacket:
由於 UDP 沒有服務端和客戶端之分,因此咱們把兩端分別定義爲 發送端 和 接收端。
發送端代碼:spa
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class UDPSender { private static final int RECVER_LISTENING_PORT = 9999; public static void main(String[] args) throws Exception { List<DatagramPacket> messages = new ArrayList<>(12); for (int i = 0; i < 4; i++) { String msgBody = (i == 3 ? "" : "Hello UDP-" + i); DatagramPacket msg0 = parseMsg( msgBody, "127.0.0.1", RECVER_LISTENING_PORT); // 發送給本機 DatagramPacket msg1 = parseMsg( msgBody, "192.168.3.3", RECVER_LISTENING_PORT); // 發送給同一局域網的一臺機器 DatagramPacket msg2 = parseMsg( msgBody, "120.77.**.***", RECVER_LISTENING_PORT); // 120.77.**.*** 是我阿里雲主機的公網 IP 地址 // JDK1.5 時 Collections 添加的 addAll 方法,能夠一次往某個集合中添加多個元素 Collections.addAll(messages, msg0, msg2, msg1); } startSending(messages); } private static void startSending(List<DatagramPacket> messages) throws IOException, InterruptedException { // 無參構造的 DatagramSocket 會隨機選擇一個端口進行監聽 // 由於此時 DatagramSocket 的做用是發送,因此無需顯式指定固定端口 try (DatagramSocket socket = new DatagramSocket()) { System.out.println("隨機給發送端分配的端口爲:" + socket.getLocalPort() + "\n"); for (DatagramPacket msg : messages) { socket.send(msg); // 發送數據包 int recverPort = msg.getPort(); InetAddress recverAddr = msg.getAddress(); System.out.printf("消息已經發送 -> (%s:%d)\n", recverAddr.getHostAddress(), recverPort); Thread.sleep(500); // 設定 每隔 0.5 秒發送一個消息 } } } private static DatagramPacket parseMsg(String msgBody, String addr, int port) throws UnknownHostException { byte[] msgData = msgBody.getBytes(); DatagramPacket msg = new DatagramPacket( msgData, 0, msgData.length, // 數據從位置 0 開始,長度爲 msgData.length InetAddress.getByName(addr), port); // 目的地 地址爲 addr,監聽端口爲 port return msg; } }
接收端代碼:.net
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class UDPReceiver { private static final int LISTENING_PORT = 9999; private static final int BUFFER_SIZE = 512; public static void main(String[] args) throws Exception { byte[] buffer = new byte[BUFFER_SIZE]; DatagramPacket msg = new DatagramPacket(buffer, buffer.length); try (DatagramSocket socket = new DatagramSocket(LISTENING_PORT)) { System.out.println("接收端已經啓動...\n"); while (true) { socket.receive(msg); // 接收數據包 String msgBody = new String( msg.getData(), msg.getOffset(), msg.getLength()); if (msgBody.isEmpty()) { // 發現接收的消息是空字符串("")便跳出循環 break; } int senderPort = msg.getPort(); InetAddress senderAddr = msg.getAddress(); System.out.printf("發送端 地址和端口 -> (%s:%d)\n", senderAddr.getHostAddress(), senderPort); System.out.println("發送端 發送的消息 -> " + msgBody + "\n"); } } System.out.println("接收端已經關閉。"); } }
如今 在本機、同一局域網的一臺機器和阿里雲主機上都運行 UDPReceiver:
code
而後啓動發送端 UDPSender:
視頻
接收端接收結果:
開發
能夠看到每一個接收端都正確的接收了發送端發送的消息。字符串
(本機 和 局域網機器 顯示的發送端的端口(49756)是一致的,可是遠程機器即阿里雲主機顯示的(4802)卻與他們不一致——這個問題留給有興趣的讀者本身思考)
雖然 UDP 是不可靠的協議,可是由於它不須要創建鏈接,效率更快,因此在 須要不創建鏈接便可發送數據 的系統(好比本文的例子),或者 保證最快的傳輸速度比每一位數據都正確更重要 的系統中,咱們應該使用 UDP。固然,基於 UDP 咱們一樣能夠開發出可靠的協議——數據包的正確與否能夠交給應用程序來判斷,若是有問題接收端便提示發送端從新發送。