Java-Day-29( UDP 网络通信编程 + 章节作业一二三小注 )
Java-Day-29
UDP 网络通信编程 ( 了解 )
基本介绍
-
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序
- DatagramSocket:数据报套接字
- DatagramPacket:数据包 / 数据报
-
UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达 ( 所以是不可靠的 )
-
DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号
-
UDP 协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接
-
UDP 说明:
-
程序 / 发送端 ( 内含 DatagramSocket ) —— DatagramPacket ( 内含数据、ip、端口等 ) ——> 程序 / 接收端 ( 内含 DatagramSocket )
-
没有明确的服务端和客户端 ( 因为没有建立连接 ),演变成数据的发送端和接收端
-
接收数据和发送数据是通过 DatagramSocket 对象完成
-
将数据封装到 DatagramPacket 对象 ( 装包 ),然后发送
-
当接收到 DatagramPacket 对象,需要进行拆包,取出数据
-
DatagramSocket 可以指定在哪个端口接收数据
-
-
基本流程:
- 核心的两个类 / 对象 DatagramSocket 与 DatagramPacket
- 建立发送端,接收端
- 发送数据前,建立数据包 / 报 DatagramPacket 对象
- 调用 DatagramSocket 的发送、接收方法
- 关闭 DatagramSocket 释放资源
应用案例
-
编写一个接收端 A 和一个发送端 B
-
接收端 A 在 9999 端口等待接收数据 ( receive )
-
发送端 B 向接收端 A 发送数据 “ hello 哇 ”
-
接收端 A 接收到发送端 B 发送的数据,回复 " 你也 hello 哇 " 再退出
-
发送端 B 接收回复的数据再退出
-
接收端 A:UDPReceiver_A
public class UDPReceiver_A { public static void main(String[] args) throws IOException { // 1. 创建一个DatagramSocket对象,准备在9999接收数据 DatagramSocket socket = new DatagramSocket(9999); // 2. 构建一个DatagramPacket对象,准备接收数据 // 在前面讲解 UDP 协议时,老师说过一个数据包,最大是64K // byte[] buf = new byte[64 * 1024]; byte[] buf = new byte[1024]; // 题目的一句话,那1024就足够了 DatagramPacket packet = new DatagramPacket(buf, buf.length); // 3. 调用接收方法,通过网络传输的 DatagramPacket 对象 // 填充到 packet 对象 // 注意:当有数据包发送到 9999 端口时,就会接收到数据 // 如果没有数据包发送到本机的 9999 端口,就会阻塞等待 System.out.println("接收端A等待接收数据中......"); socket.receive(packet); // 4. 可以把 packet 进行拆包,取出数据并显示 int length = packet.getLength(); // 实际接收的数据字节长度(实际不一定有1024) byte[] data = packet.getData(); // 接收到数据 String s = new String(data, 0, length); System.out.println(s); // 5. 回复信息给B端 data = "你也 hello 哇".getBytes(); // new DatagramPacket(发送内容(字节数组),长度,主机(IP),端口) // InetAddress.getLocalHost() 是指定了自己给自己发,实际上这种情况都少 ---- 主机IP在命令行 ipconfig 自行查看 packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.103.0.241"), 9998); socket.send(packet); // 发送 // 关闭资源 socket.close(); System.out.println("A端已退出"); } }
-
发送端 B:UDPSender_B
public class UDPSender_B { public static void main(String[] args) throws IOException { // 1. 创建 DatagramSocket 对象,准备发送和接收数据 // 因为是另一端,所以不能和A用一个端口,packet里面有存对方的端口,若是都为9999就乱了 DatagramSocket socket = new DatagramSocket(9998); // 2. 将需要发送的数据,封装到 DatagramPacket 对象 byte[] data = "hello 哇".getBytes(); // new DatagramPacket(发送内容(字节数组),长度,主机(IP),端口) // InetAddress.getLocalHost() 是指定了自己给自己发,实际上这种情况都少 ---- 主机IP在命令行 ipconfig 自行查看 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.103.0.241"), 9999); socket.send(packet); // 3. 接收从A端回复的信息 (A前部分代码) byte[] buf = new byte[1024]; // 题目的一句话,那1024就足够了 packet = new DatagramPacket(buf, buf.length); socket.receive(packet); int length = packet.getLength(); // 实际接收的数据字节长度(实际不一定有1024) data = packet.getData(); // 接收到数据 String s = new String(data, 0, length); System.out.println(s); // 关闭资源 socket.close(); System.out.println("B端已退出"); } }
章节作业
-
题目一
-
使用字符流的方式,编写一个客户端程序和服务端程序
-
客户端发送 " name ",服务端接收到后,返回 " 我是 zyz "
-
客户端发送 " hobby ",服务端接收到后,返回 " 编写 java程序 "
-
不是这两个问题,回复 " 你说啥哩 "
-
服务端:
public class Homework01Server { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端8888端口监听等待......"); Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); // 写入的用newLine结束,收到的用readLine结束 System.out.println(s); String answer = ""; if ("name".equals(s)) { answer = "我是zyz"; } else if ("hobby".equals(s)) { answer = "编写java程序"; } else { answer = "你说啥嘞"; } // 读取后回复 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write(answer); bufferedWriter.newLine(); // 插入换行符,表示回复内容的结束 bufferedWriter.flush(); // 注意别忘了手动的 flush // 关闭其他资源 socket.close(); serverSocket.close(); bufferedReader.close(); bufferedWriter.close(); } }
-
客户端:
public class Homework01Client { public static void main(String[] args) throws Exception { Socket socket = new Socket(InetAddress.getLocalHost(), 8888); OutputStream outputStream = socket.getOutputStream(); // 写入到数据通道,用字符流 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); // 从键盘读取用户的问题 Scanner scanner = new Scanner(System.in); System.out.println("请输入你的问题"); String question = scanner.next(); bufferedWriter.write(question); // 通过bufferedWriter将问题写到数据通道 bufferedWriter.newLine(); //插入换行符表示内容接收(注意:对方要用readLine) bufferedWriter.flush(); // 获取和socket关联的输入流,读取数据(字符)并显示 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); System.out.println(s); bufferedReader.close(); bufferedWriter.close(); socket.close(); System.out.println("客户端退出......"); } }
-
-
题目二
-
编写一个接收端 A 和一个客户端 B,使用 UDP 协议完成
-
接收端在 8888 端口等待接收数据 ( receive )
-
发送端向接收端发送数据 " 四大名著是哪些子 "
-
接收端接收到发送端发送的问题后,返回 “ 四大名著是 《 西游记 》、《 水浒传 》...... ”,否则返回 " what ? "
-
接收端和发送端程序退出
-
接收端:
public class Homework02ReceiverA { public static void main(String[] args) throws IOException { // 1. 创建一个DatagramSocket对象,准备在9999接收数据 DatagramSocket socket = new DatagramSocket(9999); // 2. 构建一个DatagramPacket对象,准备接收数据 byte[] buf = new byte[1024]; // 题目的一句话,那1024就足够了 DatagramPacket packet = new DatagramPacket(buf, buf.length); // 3. 调用接收方法,通过网络传输的 DatagramPacket 对象填充到 packet 对象 // 注意:当有数据包发送到 9999 端口时,就会接收到数据,否则就阻塞等待 System.out.println("接收端A等待接收数据中......"); socket.receive(packet); // 4. 可以把 packet 进行拆包,取出数据并显示 int length = packet.getLength(); // 实际接收的数据字节长度 byte[] data = packet.getData(); // 接收到数据 String s = new String(data, 0, length); // 字符串读出 String answer = ""; // 初始化回复数据 // 判断接收到的信息是什么 if ("四大名著是哪些子".equals(s)) { answer = "四大名著是 《 西游记 》、《 水浒传 》......"; } else { answer = "what ?"; } // 5. 回复信息给B端 data = answer.getBytes(); // new DatagramPacket(发送内容(字节数组),长度,主机(IP),端口) packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.105.15.16"), 9998); socket.send(packet); // 发送 // 关闭资源 socket.close(); System.out.println("A端已退出"); } }
-
发送端:
public class Homework02SenderB { public static void main(String[] args) throws IOException { // 1. 创建 DatagramSocket 对象,准备发送和接收数据 DatagramSocket socket = new DatagramSocket(9998); // 2. 将需要发送的数据,封装到 DatagramPacket 对象 Scanner scanner = new Scanner(System.in); System.out.println("请输入你的问题: "); String question = scanner.next(); byte[] data = question.getBytes(); // new DatagramPacket(发送内容(字节数组),长度,主机(IP),端口) DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.105.15.16"), 9999); socket.send(packet); // 3. 接收从A端回复的信息 (A前部分代码) byte[] buf = new byte[1024]; // 题目的一句话,那1024就足够了 packet = new DatagramPacket(buf, buf.length); socket.receive(packet); int length = packet.getLength(); // 实际接收的数据字节长度(实际不一定有1024) data = packet.getData(); // 接收到数据 String s = new String(data, 0, length); System.out.println(s); // 关闭资源 socket.close(); System.out.println("B端已退出"); } }
- 注意:本机在 InetAddress.getByName 填写 IP 时,因为随机分配,所以 IP 会变化,要先 ipconfig 查下
-
-
题目三
-
编写客户端程序和服务器端程序
-
客户端可以输入一个音乐文件名,比如:解脱
-
服务器端收到音乐名后,可以给客户端返回这个音乐文件,如果服务器端没有这个文件,返回一个默认的音乐即可
-
客户端收到文件后,保存到本地 e:\\
-
提示:该程序可以使用 StreamUtils.java ( 此题只用了第一个转为字节数组的方法 )
public class StreamUtils { /** * 功能:将输入流转换成byte[],即可以把文件的内容读入到byte[] */ public static byte[] streamToByteArray(InputStream is) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); //创建输出流对象 byte[] b = new byte[1024]; // 字节数组 int len; while((len = is.read(b)) != -1) { // 循环读取 bos.write(b, 0, len); // 把读取到的数据写入bos } byte[] array = bos.toByteArray(); // 然后将bos转成字节数组(就是将图片转成程序内文件字节数组的过程) bos.close(); return array; } public static String streamToString(InputStream is) throws Exception { /** * 功能:输入流数据直接转成字符串 */ BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder builder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { builder.append(line + "\r\n"); } return builder.toString(); } }
-
本质:其实就是指定下载文件的应用
-
服务端
public class Homework03Server { public static void main(String[] args) throws Exception { // 1. 监听 9999 端口,等待客户端连接 ServerSocket serverSocket = new ServerSocket(9999); // 2. 等待客户端连接 System.out.println("服务端,在9999端口监听,等待下载文件"); Socket socket = serverSocket.accept(); // 3. 读取客户端发送的要下载的文件名 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int readLen = 0; String downLoadFileName = ""; // 这里用while循环是考虑到将来客户端发送的数据较大的情况 while ((readLen = inputStream.read(bytes)) != -1) { downLoadFileName += new String(bytes, 0, readLen); } System.out.println("客户端希望下载的文件名为 = " + downLoadFileName); String resFileName = ""; if ("解脱".equals(downLoadFileName)) { resFileName = "src\\main\\webapp\\img\\解脱.mp3"; } else { resFileName = "src\\main\\webapp\\img\\默认.mp3"; } // 创建一个输入流,来读取文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(resFileName)); // 使用工具类读取文件到一个字节数组中 byte[] bytes1 = StreamUtils.streamToByteArray(bis); // 输入流转成字节数组 // 得到Socket关联的输出流 BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); // 写入到数据通道,返回给客户端 bos.write(bytes1); socket.shutdownOutput(); // 设置结束标记,单方面关闭输入流,socket没有关闭 // 关闭相关的资源 bis.close(); inputStream.close(); socket.close(); serverSocket.close(); System.out.println("服务端已退出......"); } }
-
客户端
public class Homework03Client { public static void main(String[] args) throws Exception { // 1. 用户输入,指定下载文件名 Scanner scanner = new Scanner(System.in); System.out.println("请输入下载文件名"); String downloadFileName = scanner.next(); // 2. 连接服务端,准备发送 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); // 3. 获取和 Socket关联的输出流,输出文件名 OutputStream outputStream = socket.getOutputStream(); outputStream.write(downloadFileName.getBytes()); // 设置写入结束的标志(否则服务端的while循环不知道结束) socket.shutdownOutput(); // 4. 读取服务端返回的文件(字节数据),即获取刚才输入的想下载的文件 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); byte[] bytes = StreamUtils.streamToByteArray(bis); // 接收InputStream转换、组装成字节数组 // 5. 得到一个输出流,准备将 bytes 写入到磁盘文件 String filePath = "e:\\" + downloadFileName + ".mp3"; // 路径实际是自行设置 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); bos.write(bytes); bos.close(); bis.close(); outputStream.close(); socket.close(); System.out.println("客户端下载完毕,正确退出......"); } }
-
-
小注
-
close():套接字整个关闭
-
shutdown..put():告诉连接的另一端,我已完成所有数据的发送,同时还要保持接收数据的能力 ( 将输入或输出相互独立的关闭 ) —— 字节流使用
-
flush():手动刷新,刷后的结尾要 close 整个关闭—— 字符流使用 ( 文件较大,用到了缓冲区,即内存,所以要将其中内容强制写入 )
- 字符:一般 flush 前还要 newLine 插入一个换行符表结束
- 字节:flush 可不写 ( 因为不管写不写都会把数据保存到字节流上 ) ,但后面必须要 socket.shutdownOutput() 写入结束标记
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义