当前位置: 首页 > backend >正文

网络编程 05:UDP 连接,UDP 与 TCP 的区别,实现 UDP 消息发送和接收,通过 URL 下载资源

一、概述

记录时间 [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://指定用于访问资源的协议。常见的有 httphttpsftpmailtofile。它告诉浏览器或应用程序使用哪种规则来获取资源。
Authority(授权部分)www.example.com:8080通常包含主机名 Host ** 和端口 Port**。
Host(主机)www.example.com资源所在服务器的域名或 IP 地址。
Port(端口):8080HTTP 默认端口是 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

http://www.xdnf.cn/news/19649.html

相关文章:

  • EPLAN 分散式端子:提升原理图设计效率的实用功能
  • 使用 C 模仿 C++ 模板的拙劣方法
  • Replit在线编程工具:支持多语言环境免配置与实时协作,助力编程学习调试与社区项目复用
  • 企业微信员工聊天记录能看吗?合规管理三要素一次性说清
  • cuDNN深度解析:实战演练
  • Electron 菜单与托盘:构建用户友好的界面元素
  • 9月2日
  • 深入分析 json2(新)与标准的 jsonrpc的区别
  • zephyr设备树的硬件描述转换为c语言
  • Hash 算法 SHA-1、SHA-256、SHA-384、SHA-512 对比
  • SpringBoot3 + Netty + Vue3 实现消息推送(最新)
  • 食品分类案例
  • 码住!辉芒微MCU型号规则详细解析
  • Kafka 架构详解
  • 动子注册操作【2025.9.2学习记录】
  • MVP架构深层剖析-从六大设计原则的实现角度到用依赖注入深度解耦
  • Elasticsearch 核心知识与常见问题解析
  • MCU上跑AI—实时目标检测算法探索
  • 【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
  • 空间不足将docker挂载到其他位置
  • 03_网关ip和端口映射(路由器转发)操作和原理
  • 梯度消失问题:深度学习中的「记忆衰退」困境与解决方案
  • React 学习笔记4 Diffing/脚手架
  • 2025了,你知道electron-vite吗?
  • 网络原理——HTTP/HTTPS
  • ImageMagick命令行图片工具:批量实现格式转换与压缩,支持水印添加及GIF动态图合成
  • 2条命令,5秒安装,1秒启动!Vite项目保姆级上手指南
  • 鸿蒙NEXT界面交互全解析:弹出框、菜单、气泡提示与模态页面的实战指南
  • 开源的聚合支付系统源码/易支付系统 /三方支付系统
  • Erlang 利用 recon 排查热点进程