亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

如何創建 Java UDP Socket

1. 前言

UDP 的英文全稱是:User Datagram Protocol,翻譯成中文叫用戶數據報協議,它是 TCP/IP 協議族中一個非常重要的傳輸層協議。UDP 是一個無連接的、不可靠的傳輸層協議,沒有丟包重傳機制、沒有流控機制、沒有擁塞控制機制。UDP 不保證數據包的順序,UDP 傳輸經常出現亂序,UDP 不對重復包進行過濾。

既然 UDP 這么多缺點,我們還有學習的必要嗎?其實不然,正是因為 UDP 沒有提供復雜的各種保障機制,才使得它具有實時、高效的傳輸特性。那么 UDP 到底有哪些優勢呢?

  • 第一,UDP 是面向消息的傳輸協議,保證數據包的邊界,不需要進行消息解析,處理邏輯非常簡單。
  • 第二,UDP 具有實時、高效的傳輸特性。
  • 第三,協議棧沒有對 UDP 進行過多的干預,這給應用層帶來了很多便利,應用程序可以根據自己的需要對傳輸進行控制。比如,自己實現優先級控制、流量控制、可靠性機制等。當然還有其他一些優勢,我就不再一一列舉。

UDP 在音視頻會議、VOIP、音視頻實時通信等行業有著廣泛的應用。為此,我們是非常有必要學好 UDP 的。由于 UDP 相對簡單,學習起來也會輕松很多。

2. 傳統 UDP 客戶端和服務器建立過程

同樣,我們展示了通過 C 語言 Socket API 編寫 UDP 客戶端和服務器程序的步驟,如下:
圖片描述

圖中的矩形方框都是 C 函數。對比 TCP 客戶端、服務器的建立過程,我們發現 UDP 客戶端可以調用 connect 函數,但是并不會去連接服務器,只是和本地接口做綁定;UDP 服務器是沒有 listen 和 accept 調用的。對于 UDP 客戶端來說,connect 函數的調用是可選的。接下來,我們就探討一下如何用 Java 語言編寫 UDP 客戶端和服務器程序。

3. Java DatagramSocket 分析

Java 語言抽象了 java.net.DatagramSocket 類,表示一個 UDP Socket,既可以用在客戶端,又可以用在服務器端。java.net.DatagramSocket 是一個包裝類,對外抽象了一組方法,具體實現是在 java.net.DatagramSocketImpl 類中完成的,它允許用戶自定義具體實現。java.net.DatagramSocket 類包含的主要功能如下:

  • 創建 UDP Socket,具體就是創建一個 java.net.DatagramSocket 類的對象。
  • 將 Socket 綁定到本地接口 IP 地址或者端口,可以調用 java.net.DatagramSocket 類的構造方法bind 方法完成。
  • 將客戶端 UDP Socket 和遠端 Socket 做綁定,可以通過 java.net.DatagramSocket 類的 connect 方法完成。

提示:
UDP 客戶端調用 connect 方法,僅僅是將本地 Socket 和遠端 Socket 做綁定,并不會有類似 TCP 三次握手的過程。

  • 關閉連接,可以調用 java.net.DatagramSocket 類的 close 方法完成。

  • 接收數據,可以通過 java.net.DatagramSocket 類的 receive 方法實現數據接收。

  • 發送數據,可以通過 java.net.DatagramSocket 類的 send 方法實現數據發送。

java.net.Socket 類提供了一組重載的構造方法,方便程序員選擇,大體分為四類:

  • 無參
public DatagramSocket() throws SocketException

綁定到任意可用的端口和通配符 IP 地址,比如 IPv4 的 0.0.0.0。一般用作 UDP 客戶端 Socket 的創建。

  • 傳入 port 參數
public DatagramSocket(int port) throws SocketException

綁定到由 port 指定的端口和通配符 IP 地址,比如 IPv4 的 0.0.0.0。一般用作 UDP 服務端 Socket 的創建。

  • 傳入指定的 IP 和 Port 參數
public DatagramSocket(SocketAddress bindaddr) throws SocketException
public DatagramSocket(int port, InetAddress laddr) throws SocketException

綁定到指定的端口和指定的網絡接口。如果你的主機有多個網卡,并且你指向在某個指定的網卡上收發數據,可以用此構造方法。既可以用作 UDP 客戶端 Socket,也可以用作 UDP 服務端 Socket。

4. Java UDP 客戶端

我們創建一個簡單的 UDP 客戶端程序,代碼如下:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

public class UDPClient {
    private static final int PORT = 9002;
    private static final String DST_HOST = "127.0.0.1";
    private static final int RECV_BUFF_LEN = 1500;
    private static byte[] inBuff = new byte[RECV_BUFF_LEN];

    public static void main(String[] args) {
        // 創建 UDP 客戶端 Socket,選擇無參構造方法,由系統分配本地端口號和網絡接口
        try (DatagramSocket udpClient = new DatagramSocket()){
            // 構造發送的目標地址,指定目標 IP 和目標端口號
            SocketAddress to = new InetSocketAddress(DST_HOST, PORT);
            while (true){
                String req = "Hello Server!";
                // 構造發送數據包,需要傳入消息內容和目標地址結構 SocketAddress
                DatagramPacket message = new DatagramPacket(req.getBytes(), req.length(), to);
                // 發送消息
                udpClient.send(message);
                System.out.println("Send UDP message:"
                        + req + " to server:" + message.getSocketAddress().toString());

                // 構造接收消息的數據包,需要傳入 byte 數組
                DatagramPacket inMessage = new DatagramPacket(inBuff, inBuff.length);
                // 接收消息
                udpClient.receive(inMessage);

                System.out.println("Recv UDP message:"
                        + new String(inMessage.getData(), 0, inMessage.getLength())
                        + " from server:" + inMessage.getSocketAddress().toString());

                // 每隔 2 秒發送一次消息
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. Java UDP 服務端

我們創建一個簡單的 UDP 服務端程序,代碼如下:


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    private static final int BIND_PORT = 9002;
    private static final String BIND_HOST = "127.0.0.1";
    private static final int RECV_BUFF_LEN = 1500;
    private static byte[] inBuff = new byte[RECV_BUFF_LEN];

    public static void main(String[] args) {
        // 構造服務器 Socket,綁定到一個固定的端口,監聽的 IP 是 0.0.0.0
        try (DatagramSocket udpServer = new DatagramSocket(BIND_PORT)) {
            // 構造接收消息的數據包,需要傳入 byte 數組。
            // 我們將這條語句放在循環外,不需要每次消息收發都構造此結構
            DatagramPacket inMessage = new DatagramPacket(inBuff, inBuff.length);
            while (true){
                // 接收客戶端消息
                udpServer.receive(inMessage);
                System.out.println("Recv UDP message:"
                        + new String(inMessage.getData(), 0, inMessage.getLength())
                        + " from Client:"
                        + inMessage.getSocketAddress().toString());

                String rsp = "Hello Client!";
                // 構造發送的消息結構
                // 注意!?。τ诜掌鱽碚f,發送的目標地址一定是接收消息時的源地址,所以從 inMessage 結構獲取
                DatagramPacket message = new DatagramPacket(rsp.getBytes(), rsp.length(),
                        inMessage.getSocketAddress());
                // 發送消息
                udpServer.send(message);
                System.out.println("Send UDP message:"
                        + rsp + " to Client:" + message.getSocketAddress().toString());
                // 重置接收數據包消息長度,準備接收下一個消息
                inMessage.setLength(inBuff.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6. 小結

用 Java 語言編寫 UDP 客戶端和服務器程序非常簡單,你只需要創建一個 java.net.DatagramSocket 實例。如果你構造的是服務器 Socket,需要傳入監聽的端口號,監聽的接口 IP 是可選的,默認是監聽通配符 IP。如果你創建的是客戶端 Socket,你可以傳入綁定的本地 Port 和接口地址,也可以不傳入任何參數。對于客戶端 UDP Socket 來說,你也可以調用 connect 方法,只是和遠端 Socket 綁定,沒有類似 TCP 的三次握手過程。

示例代碼我們采用的是 try-with-resources 寫法,對于 Java 7 以后的版本,可以不顯式調用 close 方法。如果是非此寫法,需要顯式調用 close 方法。