一、概述
記錄時間 [2025-09-02]
前置文章:
網絡編程 01:計算機網絡概述,網絡的作用,網絡通信的要素,以及網絡通信協議與分層模型
網絡編程 02:IP 地址,IP 地址的作用、分類,通過 Java 實現 IP 地址的信息獲取
網絡編程 03:端口的定義、分類,端口映射,通過 Java 實現了 IP 和端口的信息獲取
網絡編程 04:TCP連接,客戶端與服務器的區別,實現 TCP 聊天及文件上傳,Tomcat 的簡單使用
本文講述網絡編程相關知識——UDP 連接,包括 UDP 的核心特點,UDP 與 TCP 的區別,以及在 Java 中實現 UDP 消息發送和接收,通過 URL 下載資源等。
二、UDP
1. UDP 的核心特點
UDP(User Datagram Protocol,用戶數據報協議)是一種簡單的、無連接的、不可靠的傳輸層協議。
- 無連接
- UDP 發送數據之前不需要先建立連接,減少了通信的延遲。
- 不可靠交付
- UDP 不提供任何機制來確認數據是否成功到達目的地,也不保證數據包的送達順序。
- 無擁塞控制
- UDP 以恒定的速率發送數據,而不管網絡是否擁堵,容易丟包。這對于網絡整體穩定性可能是個缺點,但對于需要恒定速率的應用卻是優點。
- 數據報結構
- UDP 保留了應用程序定義的消息邊界。如果發送方發送了 5 個 UDP 數據報,接收方將會收到 5 個獨立的數據報。
- 而 TCP 則是一個字節流,應用程序需要自己解析消息的開始和結束。
2. UDP 與 TCP 的區別
通過將 UDP 與 TCP 對比來更好地理解它:
特性 | TCP (傳輸控制協議) | UDP (用戶數據報協議) |
---|---|---|
連接 | 面向連接的 | 無連接的 |
通信前必須先建立連接(三次握手) | 無需建立連接,直接發送數據 | |
可靠性 | 可靠的 | 不可靠的 |
確保數據按序、完整地送達,有重傳機制 | 不保證數據送達,也不保證順序 | |
傳輸速度 | 相對較慢(由于握手、確認、重傳等開銷) | 非常快(開銷極小) |
數據流 | 字節流,無消息邊界 | 數據報,有消息邊界 |
擁塞控制 | 有復雜的擁塞控制算法 | 無擁塞控制 |
應用場景 | 網頁瀏覽(HTTP)、電子郵件(SMTP)、文件傳輸(FTP) | 視頻流、語音通話、在線游戲、DNS查詢 |
3. UDP 在 Java 的關鍵類
在 Java 中,使用 UDP 協議進行網絡通信主要涉及兩個類:DatagramPacket 和 DatagramSocket。
DatagramPacket
:- 用于發送和接收數據報包的套接字;
- 表示一個數據報包,用于存儲要發送或接收的數據;
- 包含了數據本身以及目標地址(IP 地址和端口號)。
DatagramSocket
:用于發送和接收DatagramPacket
的套接字。- 表示數據報包,包含數據和目標地址信息;
- 用于發送時指定數據和目標地址;
- 用于接收時提供緩沖區存儲接收到的數據。
三、UDP 消息發送和接收
注意:UDP 中沒有明確的客戶端、服務端的概念,也不需要建立雙向連接。我們在這里把發消息的稱為發送端,接收消息的稱為接收端。
1. 簡單發送和接收
發送端
數據包 package 中包含:數據(字節 byte 類型),數據的長度(起始,結束), 對方 ip,對方端口。
import java.net.*;// 發送端
public class UdpSendDemo01 {public static void main(String[] args) throws Exception {// 1. 建立一個 socket, 開放端口DatagramSocket socket = new DatagramSocket();// 2. 準備一個數據包String msg = "這是一個數據包";InetAddress inetAddress = InetAddress.getByName("127.0.0.1");// 數據包, 數據的長度起始, 結束, 對方ip, 對方端口DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, inetAddress, 9000);// 3. 發送數據包socket.send(packet);System.out.println("Message sent to the server.");// 4. 關閉資源socket.close();}
}
接收端
接收包的程序要先打開,只有開著才能收到消息。
因為 UDP 只管發,不會去管接收端有沒有準備好的。
import java.net.*;// 接收端
public class UdpReceiveDemo01 {public static void main(String[] args) throws Exception {// 1. 開放端口DatagramSocket socket = new DatagramSocket(9000);// 2. 接收數據包byte[] buffer = new byte[1024];DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);// 阻塞接收socket.receive(packet);// 3. 查看數據包System.out.println(packet.getAddress().getHostAddress());System.out.println(new String(packet.getData(), 0, packet.getLength()));// 4. 關閉資源socket.close();}
}
2. 循環發送和接收
在簡單 UDP 消息發送的基礎上,給程序增加循環,實現 UDP 消息循環發送和接收。
并增加判斷條件:當發送過來的內容是 bye
時,程序結束。
發送端
import java.io.*;
import java.net.*;public class UdpSender {public static void main(String[] args) throws Exception {// 1. 開放端口DatagramSocket socket = new DatagramSocket(8888);// 2. 裝包// 從鍵盤輸入到控制臺 System.in, 控制臺讀取// 用 BufferedReader 去讀鍵盤輸入到控制臺的內容BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));while (true) {// 讀一整行String data = reader.readLine();// 轉成字節流, socket 發的是字節流byte[] dataBytes = data.getBytes();DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("localhost", 6666));// 3. 發包socket.send(packet);// 本地退出if (data.equals("bye")) {break;}}// 4. 關閉資源socket.close();}
}
接收端
發過來的內容是字節 byte 類型的,要轉換成字符串 String 類型。
import java.net.DatagramPacket;
import java.net.DatagramSocket;public class UdpReceiver {public static void main(String[] args) throws Exception {// 1. 開放端口DatagramSocket socket = new DatagramSocket(6666);// 準備一個容器byte[] container = new byte[1024];while (true) {// 2. 讀包DatagramPacket packet = new DatagramPacket(container, 0, container.length);// 阻塞接收包socket.receive(packet);// 3. 輸出包// 包是字節流, 轉成 stringbyte[] data = packet.getData();String receiveData = new String(data, 0, packet.getLength());// 輸出內容System.out.println(receiveData);// 遠程退出if (receiveData.equals("bye")) {break;}}// 4. 關閉資源socket.close();}
}
四、UDP 多線程在線咨詢
特點:互發消息。
在了解 UDP 發送、接收消息的邏輯后,我們來實現如下程序功能。
- 相當于一個咨詢平臺:學生向老師咨詢問題,老師給出答復。
- 接收端、發送端是兩個多線程。
- 老師端、學生端是兩個用戶,他們既可以發消息,也可以接收消息。
更多多線程相關的知識,請參考 - 這篇文章
這里,通過實現 Runnable 接口來創建線程。
1. 接收端
接收端用于接收 UDP 消息。
import java.io.IOException;
import java.net.*;public class TalkReceive implements Runnable {DatagramSocket socket = null;// 接收端的 portprivate int port;// 消息從哪里來private String msgFrom;public TalkReceive(int port, String msgFrom) {this.port = port;this.msgFrom = msgFrom;try {// 1. 開放端口this.socket = new DatagramSocket(this.port);} catch (SocketException e) {e.printStackTrace();}}@Overridepublic void run() {// 準備一個容器byte[] container = new byte[1024];while (true) {try {// 2. 讀包DatagramPacket packet = new DatagramPacket(container, 0, container.length);// 阻塞接收包socket.receive(packet);// 3. 輸出包// 包是字節流, 轉成 Stringbyte[] data = packet.getData();String receiveData = new String(data, 0, packet.getLength());// 輸出內容System.out.println(msgFrom + ": " + receiveData);// 斷開連接, 遠程退出if (receiveData.equals("bye")) {break;}} catch (IOException e) {e.printStackTrace();}}// 4. 關閉資源socket.close();}
}
2. 發送端
發送端用于發送 UDP 消息。
import java.io.*;
import java.net.*;public class TalkSend implements Runnable {DatagramSocket socket = null;BufferedReader reader = null;// 接收的地址 (接收 ip, 接收 port)private String toIP;private int toPort;// 從哪里來private int fromPort;public TalkSend(String toIP, int toPort, int fromPort) {this.fromPort = fromPort;this.toIP = toIP;this.toPort = toPort;try {// 1. 開放端口this.socket = new DatagramSocket(this.fromPort);// 從鍵盤輸入到控制臺 System.in, 控制臺讀取// 用 BufferedReader 去讀鍵盤輸入到控制臺的內容reader = new BufferedReader(new InputStreamReader(System.in));} catch (SocketException e) {e.printStackTrace();}}@Overridepublic void run() {try {while (true) {// 2. 裝包// 讀一整行String data = reader.readLine();// 轉成字節流, socket 發的是字節流byte[] dataBytes = data.getBytes();DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress(toIP, toPort));// 3. 發包socket.send(packet);// 本地退出if (data.equals("bye")) {break;}}} catch (IOException e) {e.printStackTrace();}// 4. 關閉資源socket.close();}
}
3. 老師端
模擬老師的操作:
- 給學生發消息(創建發送端的多線程)
- 接收來自學生的消息(創建接收端的多線程)
public class TalkTeacher {public static void main(String[] args) {// 啟動多線程// 把消息發送到 localhost 的 8888 端口// 8888 是學生的 Receive 開放端口// Send 方開放的端口用不上,Receive 方開放的端口才有用new Thread(new TalkSend("localhost", 8888, 5555)).start();// 開放 9999 端口,接收來自學生的消息new Thread(new TalkReceive(9999, "student")).start();}
}
4. 學生端
模擬學生的操作:
- 給老師發消息(創建發送端的多線程)
- 接收來自老師的消息(創建接收端的多線程)
public class TalkStudent {public static void main(String[] args) {// 啟動多線程// 把消息發送到 localhost 的 9999 端口// 9999 是老師的 Receive 開放端口new Thread(new TalkSend("localhost", 9999, 7777)).start();// 開放 8888 端口,接收來自老師的消息new Thread(new TalkReceive(8888, "teacher")).start();}
}
五、URL 下載網絡資源
1. URL 概述
URL 格式
URL(Uniform Resource Locator,統一資源定位符) 是用于指定互聯網上資源(如網頁、圖像、文件等)位置和訪問方式的一種字符串。
通俗地說,它就是我們在瀏覽器地址欄里輸入的 “網址”。
一個完整的 URL 由多個部分組成,通常遵循以下格式:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]// example
https://www.example.com:8080/products/index.html?category=electronics&id=42#specs
具體的部分,內容解釋如下:
其中,www.example.com
是域名,可以通過 DNS 域名解析服務解析成對應的 IP 地址。
部分 | 例子 | 說明 |
---|---|---|
Scheme(方案) | https:// | 指定用于訪問資源的協議。常見的有 http 、https ,ftp ,mailto ,file 。它告訴瀏覽器或應用程序使用哪種規則來獲取資源。 |
Authority(授權部分) | www.example.com:8080 | 通常包含主機名 Host ** 和端口 Port**。 |
Host(主機) | www.example.com | 資源所在服務器的域名或 IP 地址。 |
Port(端口) | :8080 | HTTP 默認端口是 80,HTTPS 是 443。如果使用默認端口,通常在 URL 中省略 |
Path(路徑) | /products/index.html | 指定服務器上資源的具體位置,類似于文件系統中的文件路徑。 |
Query(查詢字符串) | ?category=electronics&id=42 | 用于向服務器傳遞額外的參數。以 ? 開頭,包含多個鍵值對(key=value),鍵值對之間用 & 分隔。 |
Fragment(片段) | #specs | 也稱為 “錨點”,它指向資源內部的某個特定部分,如 HTML 頁面中的一個標題。片段不會發送到服務器,僅在瀏覽器內部使用。 |
URL 編碼
URL 只能使用有限的 ASCII 字符集,任何包含非 ASCII 字符(如中文)或特殊字符(如空格、&
、=
)的 URL 都需要進行編碼。
URL 編碼也稱為 “百分號編碼”。
例如,空格被編碼為 %20
,中文 “中國” 被編碼為 %E4%B8%AD%E5%9B%BD
。
通過 Java 查看 URL
在 Java 中,java.net.URL
類用于表示和解析 URL。它提供了許多有用的方法來分解和操作 URL 的各個部分。
通過 Java 來查看 URL 的各個部分。
import java.net.MalformedURLException;
import java.net.URL;public class URLDemo01 {public static void main(String[] args) throws MalformedURLException {// exampleURL url = new URL("https://www.example.com:8080/products/index.html?category=electronics&id=42#specs");// 協議System.out.println(url.getProtocol());// 主機ip、域名System.out.println(url.getHost());// 端口System.out.println(url.getPort());// 文件路徑System.out.println(url.getPath());// 全路徑: 路徑+參數System.out.println(url.getFile());// 參數System.out.println(url.getQuery());}
}
對應的輸出結果:
https
www.example.com
8080
/products/index.html
/products/index.html?category=electronics&id=42
category=electronics&id=42
2. 下載 URL 資源
在 上一篇 中,講述了如何啟動 Tomcat 并訪問部署的資源。
例如,訪問自定義資源:webapps
目錄下的 test
中的 hello.txt
文件。
用到的其實就是一個 URL:
http://localhost:8080/test/hello.txt
接下來,我們來下載這個 URL 指向的網絡資源。
- 給出資源下載地址;
- 連接到這個資源;
- 通過流下載;
- 通過文件管道處理資源,保存資源。
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;public class URLDown {/*本地 tomcat 中有這樣一個文件http://localhost:8080/test/hello.txt通過 URL 下載下來*/public static void main(String[] args) throws Exception {// 1. 下載地址URL url = new URL("http://localhost:8080/test/hello.txt");// 2. 連接到這個資源 HTTPHttpURLConnection connection = (HttpURLConnection) url.openConnection();// 通過流下載InputStream is = connection.getInputStream();// 文件管道處理下載下來的數據FileOutputStream fos = new FileOutputStream(new File("NetStudy/hello.txt"));byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {// 寫出這個數據fos.write(buffer, 0, len);}// 3. 關閉資源, 斷開連接fos.close();is.close();connection.disconnect();}}
同理可得,網絡上的資源也可以這么下載,輸入 URL 即可。
無論是文本、圖片、視頻、音頻,還是其他類型的文件。
可以嘗試一下:
// 下載圖片
URL url = new URL("https://i-blog.csdnimg.cn/direct/728b14801d3f4400bad0905bfdba34be.jpeg");// 文件管道處理下載下來的數據
FileOutputStream fos = new FileOutputStream(new File("NetStudy/bfdba34be.jpeg"));
參考資料
狂神說 - 網絡編程:https://www.bilibili.com/video/BV1LJ411z7vY
Java 8 幫助文檔:https://docs.oracle.com/javase/8/docs/api/
多線程 02:線程實現,創建線程的三種方式,通過多線程下載圖片案例分析異同(Thread,Runnable,Callable):https://blog.csdn.net/Sareur_1879/article/details/141029891