【Java基礎】網絡編程-UDP編程

UDP編程基本概念

TCP/IP協議棧中,TCP協議和UDP協議的聯繫和區別?

聯繫:TCP和UDP是TCP/IP協議棧中傳輸層的兩個協議,它們使用網絡層功能數據包發送到目的地,從而爲應用層提供網絡服務。

區別
1. TCP是面向連接的傳輸。UDP是無連接的傳輸。
2. TCP保證數據按照發送順序到達,UDP無法保證。
3. TCP是可靠性傳輸,而UDP則是不可靠傳輸。
4. UDP因爲少了很多控制信息,所以傳輸速度比TCP速度快。
5. TCP適合用於傳輸大量數據,UDP適合用於傳輸小量數據。
6. Tcp是面向字節流的,udp是面向報文的;
7. Tcp只支持點對點通信,udp支持一對一,一對多,多對多的通信模式;
8. Tcp有擁塞控制機制;udp沒有擁塞控制,適合媒體通信;
9. Tcp首部開銷(20個字節)比udp的首部開銷(8個字節)要大

舉例: TCP的server和client之間通信就好比兩個人打電話。UDP的server和client之間的通信就像兩個人發電報或者發短信

  • TCP 是一種可靠的協議 —— 首先客戶端和服務端需要建立連接(三次“握手”),數據發送完畢需要斷開連接(四次“揮手”);如果發送數據時數據損壞或者丟失,那麼 TCP 會重新發送。保證可靠的代價就是效率的降低(建立連接和斷開連接就需要時間,保證數據的可靠性也需要額外的消耗)。
  • 與 TCP 相對應,UDP 是面向無連接的協議,並且它不保證數據是否會到達,也不保證到達的數據是否準確和數據順序是否正確 —— 所以相比於 TCP, UDP 的速度很快。在 不需要建立連接即可發送數據的系統,或者 保證最快的傳輸速度比每一位數據都正確更重要的系統(如視頻會議,丟失某個數據包只是一個畫面或者聲音的小干擾)中,UDP 纔是正確的選擇。實際上,在同一個網段,或者在信號很好的局域網,UDP 是非常可靠的。

UDP通訊協議的特點

  1. 將數據集封裝爲數據包面向無連接
  2. 每個數據包大小限制在64K
  3. 因爲無連接,所以不可靠
  4. 因爲不需要建立連接,所以速度快
    5.udp 通訊是不分服務端與客戶端的只分發送端與接收端

使用UDP的協議:

  • DNS:用於域名解析服務,將域名地址轉換爲IP地址。DNS用的是53號端口。
  • SNMP:簡單網絡管理協議,使用161號端口,是用來管理網絡設備的。由於網絡設備很多,無連接的服務就體現出其優勢。
  • TFTP(Trival File Transfer Protocal):簡單文件傳輸協議,該協議在熟知端口69上使用UDP服務。
  • 總的來說,相比於TCP而言,UDP不如其廣泛,但是在需要很強的實時交互性場合,如網絡遊戲和視頻會議方面,UDP相對重要。 因爲UDP協議不需要維持連接的開銷,支持一對多。多對多的通信模式。如果說TCP類似於打電話,而UDP就相當於發短信。
  • 在網絡編程中,必須要求可靠數據傳輸的信息一般使用TCP,而一般的數據使用UDP實現。

Java中的UDP編程實現

  • 和TCP編程相比,UDP編程就簡單得多,因爲UDP沒有創建連接,數據包也是一次收發一個,所以沒有流的概念。

  • 而UDP網絡編程服務端實現和TCP方式的服務器端實現類似,也是服務器端監聽某個端口,然後獲得數據報文包,進行邏輯處理後將處理以後的結果反饋給客戶端,最後關閉網絡連接。

  • 同TCP一樣,java的java.net包中,也提供了兩個類DatagramSocketDatagramPacket來支持UDP的數據包(Datagram)通信。

  • 其中DatagramSocket用於在程序之間建立傳送數據包的通信通道DatagramPacket則用來表示一個數據包。DatagramSocket發送的每個數據包都需要指定地址,而DatagramPacket則是在首次創建時指定地址,以後所有數據包的發送都通過此socket。

  • UDP的客戶端編程也是4個部分:建立連接、發送數據、接受數據和關閉連接

注意: 在Java中使用UDP編程,仍然需要使用Socket,因爲應用程序在使用UDP時必須指定網絡接口(IP)和端口號。注意:UDP端口和TCP端口雖然都使用0~65535,但他們是兩套獨立的端口,即一個應用程序用TCP佔用了端口8080,不影響另一個應用程序用UDP佔用端口8080

怎樣來編寫UDP

分別建立發送端和接收端,發送端發送數據包,接收端接收發送端發送的數據包

創建發送方步驟

  1. 創建DatagramSocket服務
  2. 創建數據包DatagramPacket ,用於發送數據,並指定ip和端口以及要發送的數據
  3. 關閉資源

創建接收方步驟

  1. 創建DatagramSocket服務,並監聽指定端口
  2. 創建數據包DatagramPacket,用來接收數據
  3. 用DatagramSocket接收數據到數據包中
  4. 從數據包DatagramPacket中取出數據
  5. 關閉資源

發送端和接收端是兩個獨立運行的程序,任何一方斷開都不會影響到對方

常用類

  • DatagramSocket:用於發送或接收數據包
    • 因爲 UDP 協議並不需要建立連接,所以我們將數據(byte數組)放入 DatagramPacket之後,還需要將目的地(IP地址和端口)放入到 DatagramPacket 中—— DatagramSocket 的 send(DatagramPacket packet)方法根據 packet 中指定目的地,將其包含的數據往這個目的地發送。至於數據是否能(準確)到達目的地,DatagramSocket 並不關心
    • DatagramSocket 在接收數據包時,我們需要爲其指定一個監聽的端口。當有包含了接收端機器 IP 地址和 DatagramSocket 所監聽端口的數據包到達時,DatagramSocket 的 receive(DatagramPacket packet) 方法便會對數據包進行接收,並將接收到的數據包填入到 packet 中。
  • DatagramPacket:數據包

不需要利用IO流實現數據的傳輸
每個數據發送單元被統一封裝成數據包的方式,發送方將數據包發送到網絡中,數據包在網絡中去尋找他的目的地

因爲 UDP 沒有服務端和客戶端之分,所以我們把兩端分別定義爲 發送端 和 接收端

DatagramSocket類

用於發送或接收數據包

方法 方法描述
void receive(DatagramPacket p) 從此套接字接收數據報包
void send(DatagramPacket p) 從此套接字發送數據報包
setSoTimeout(int timeout) 設置發送方法的超時時間,單位毫秒 (意思是後續接收UDP包時,等待時間最多不會超過1秒,否則在沒有收到UDP包時,客戶端會無限等待下去。這一點和服務器端不一樣,服務器端可以無限等待,因爲它本來就被設計成長時間運行。)
connect(InetAddress address, int port) 發送方“連接”到指定的服務器端(connect()方法不是真連接,它是爲了在客戶端的DatagramSocket實例中保存服務器端的IP和端口號,確保這個DatagramSocket實例只能往指定的地址和端口發送UDP包,不能往其他地址和端口發送。這麼做不是UDP的限制,而是Java內置了安全檢查。)
connect(SocketAddress addr) 發送方“連接”到指定的服務器端(同上)
disconnect() 用於發送方斷開連接 (disconnect()也不是真正地斷開連接,它只是清除了客戶端DatagramSocket實例記錄的遠程服務器地址和端口號,這樣,DatagramSocket實例就可以連接另一個服務器端)

DatagramPacket類

數據包

構造方法 構造方法描述
DatagramPacket(byte[] buf,int length) 構造DatagramPacket,用於接收長度爲length的數據包
DatagramPacket(byte[] buf,int length, IntAddress address, int port) 構造長度爲length的數據包。將數據發送到指定IP地址的指定端口號。
DatagramPacket(byte buf[], int length, SocketAddress address) 構造長度爲length的數據包。將數據發送到指定IP地址的指定端口號。
java.net.SocketAddress的子類爲InetSocketAddress,是java對IP+端口的封裝
DatagramPacket(byte buf[], int offset, int length, SocketAddress address) 構造長度爲length,發送起始下標爲offset的數據包, 將數據發送到指定IP地址的指定端口號
java.net.SocketAddress的子類爲InetSocketAddress,是java對IP+端口的封裝

代碼實現UDP通信

創建接收端
  1. 定義UDP的Socket服務。
  2. 定義一個數據包DatagramPacket,用於存儲接收到的字節數據。數據包對象中有更多功能可以提取字節信息中的不同信息。
  3. 通過Socket服務的receive方法將收到的數據存儲到定義好的數據包中。
  4. 通過數據包對象中的特有功能將數據取出。
  5. 對數據進行處理。
  6. 關閉資源。

定義UDP接收端的時候通常會監聽一個端口,其實就是給這個網絡應用程序定義一個數字標識。如果不定義系統會分配一個隨機的。方便於明確哪些數據過來該應用程序可以處理。接收端通常會指定一個監聽端口。

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

public class UDPRecieve {//接收端  先開啓
    public static void main(String[] args) throws IOException {
        //1.創建服務端套接字
        DatagramSocket datagramSocket = new DatagramSocket(6000);//數據包接收對象

        byte[] bytes = new byte[1024];//字節數組  用來接收 數據

        //2.創建接受客戶端信息的空數據包
        while (true) {
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);

            //3.接受數據包
            datagramSocket.receive(datagramPacket); //接收數據 存儲到dp

            //4.獲取客戶端IP和主機名
            InetAddress inet = datagramPacket.getAddress(); //獲取地址對象
            String ip = inet.getHostAddress(); //獲取IP地址
            int port = datagramPacket.getPort(); //端口號

            //5.讀取數據
            int length = datagramPacket.getLength(); //接收數據的長度
            byte[] data = datagramPacket.getData(); //接收數據的字節數組
            //String dataStr = new String(data,0,length);//顯示收到的數據
            //String dataStr = new String(bytes, 0, length);//顯示收到的數據
            // 收取到的數據存儲在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和長度
            String dataStr = new String(datagramPacket.getData(), datagramPacket.getOffset(), datagramPacket.getLength(), StandardCharsets.UTF_8);
            System.out.println("IP地址:" + ip + ",端口號:" + port + ",內容:" + dataStr);

            //6.釋放資源
            //datagramSocket.close();
        }
    }
}
  • 接收端的receive爲阻塞式方法
  • 如果系統沒有指定發送端從哪裏發送數據,系統會隨機給發送端分配一個端口,代表數據是從這裏發送的。如果發送端的端口用完之後沒有釋放則下一次發送數據的端口會按照上一次的進行順延。
創建發送端
  1. 建立UDP socket服務。
  2. 提供數據並將數據封裝到數據包中。
  3. 通過socket服務的發送功能將數據包發送出去。
  4. 關閉資源。

如果沒有接收端,因此該程序發送的數據包將會丟失。

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UDPSend {//發送端

    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);

        //1.創建客戶端套接字
        DatagramSocket datagramSocket = new DatagramSocket(); //數據包發送對象
        
        //設置發送方超時時間爲1秒
        //datagramSocket.setSoTimeout(1000);

        //2.創建客戶端發送數據包
        while (true) {
            System.out.println("請輸入要發送的信息:");
            String str = sc.nextLine();

            //要發送的數據 轉字節
            byte[] bytes = str.getBytes();

            //3. 數據包對象 (字節, 長度, 傳輸地址, 端口號)
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 6000);

            //4. 發送數據包
            datagramSocket.send(datagramPacket);

            //結束髮送循環
            if("886".equals(str)){
                break;
            }
        }
        //5.釋放資源
        //datagramSocket.close();
    }
}
  • 優先啓動接收端監聽指定端口請求

  • 發送端發送數據包
    在這裏插入圖片描述

  • 接收端接收數據包
    在這裏插入圖片描述

  • 發送端斷開並不會影響到接收端
    在這裏插入圖片描述
    在這裏插入圖片描述

使用UDP建立簡易聊天室

  • 雖然實現了使用UDP進行兩個窗口之間的通信,但是要想在一個窗口實現信息的接收與發送需要使用多線程技術。一個線程實現信息的發送,一個線程實現信息的接收。
創建接收端
**
 * 接收端
 */

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPServer implements Runnable {
    //指定端口
    private int port;

    public UDPServer(int port) {
        super();
        this.port = port;

    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public void run() {
        DatagramSocket ds = null;

        try {
            //創建UDP接收端的對象,必須指定端口
            //端口最好指定在一萬以上,因爲八千之前的端口很多都被佔用了
            ds = new DatagramSocket(port);

            //定義接收的字節數組
            byte[] bs = new byte[1024];
            System.out.println("服務器已經啓動");

            while (true) {
                //定義接收數據包
                DatagramPacket dp = new DatagramPacket(bs, bs.length);

                //數據包的接收
                ds.receive(dp);

                //獲得發送端的IP
                InetAddress ia = dp.getAddress();

                //獲得數據包中的數據,這個數組的長度是我們自己定義的長度(1024)
                byte[] bs1 = dp.getData();

                //獲取接收數據的長度(實際接收到數據的長度)
                int len = dp.getLength();

                //組裝接收到的數據
                String data = new String(bs1, 0, len);

                //退出程序
                if ("exit".equals(data)) {
                    System.out.println("接收端已退出");
                    break;
                }

                System.out.println(ia.getHostAddress() + "說:\r\n" + data);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉接收端
            if (ds != null) {
                ds.close();
            }
        }
    }
}
創建發送端
/**
 * 發送端
 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient implements Runnable {
    //發送目標的IP
    private String ip;
    private int port;

    //發送端口
    public UDPClient(String ip, int port) {
        super();
        this.ip = ip;
        this.port = port;

    }

    @Override
    public void run() {
        DatagramSocket ds = null;
        BufferedReader br = null;

        try {
            //創建控制檯的輸入流對象
            br = new BufferedReader(new InputStreamReader(System.in));

            //創建發送端端接收對象
            ds = new DatagramSocket();
            System.out.println("已經接入" + ip);

            while (true) {
                System.out.println("請輸入你要發送的內容:");

                //讀取控制檯輸入的數據並且轉換成字節數組
                byte[] bs = br.readLine().getBytes();

                //創建要發送的目的地的IP對象
                InetAddress ia = InetAddress.getByName(ip);

                //指定數據包
                //第一個參數是打包的字節數組,第二個參數是要打包的字節長度
                //第三個參數是要發送的IP對象,第四個參數是要發送的服務端
                DatagramPacket dp = new DatagramPacket(bs, bs.length, ia, port);

                //發送
                ds.send(dp);

                System.out.println("我說:\r\n" + new String(bs, 0, bs.length));

                //退出程序
                if ("exit".equals(new String(bs, 0, bs.length))) {
                    System.out.println("發送端已退出");
                    break;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            if (ds != null) {
                ds.close();

            }
            try {
                if (br != null) {
                    br.close();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

主函數
/**
 * 聊天室
 */

public class ChatRoom {
    public static void main(String[] args) {
        //創建接收端對象的線程的實現
        UDPClient uc = new UDPClient("127.0.0.1", 10000);

        //創建服務端
        UDPServer us = new UDPServer(10001);

        //發送端的線程
        Thread t = new Thread(uc);

        //接收端的線程
        Thread t1 = new Thread(us);

        //啓動線程
        t.start();
        t1.start();
    }
}

執行結果
在這裏插入圖片描述
https://www.liaoxuefeng.com/wiki/1252599548343744/1319099802058785

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章