【Java ee初阶】网络编程 UDP socket
网络编程
socket api 是传输层提供的api。
UDP 无连接,不可靠传输,面向数据报,全双工。
TCP 有链接,可靠传输,面向字节流,全双工。
UDP socket api 数据报
DatagrammSocket 代表了操作系统中的socket文件。
文件是“硬盘”硬件设备的抽象,读写文件的时候,本质上是在操作硬盘。实际上,文件在操作系统中,是更广义的概念,可以代表更多的硬件设备和软件资源。
例如,标准输入和标准输出,对应的文件就是System.in和System.out。
网卡,网卡硬件设备也是通过文件来进行封装的。通过网络发送数据,需要往网卡中写入。通过网络接收数据,需要从网卡中读取。
封装网卡的文件,我们称为“socket 文件”
使用的时候要进行的操作:
1.打开
2.读写
3.关闭
Java中创建一个DatagramSocket对象,就是在操作系统中打开了一个socket文件,这样子的socket文件,就代表了网卡。
通过这个对象写入数据,就是在通过网卡发送数据。通过这个对象读取数据,就是在通过网卡接收数据。
UDP数据报套接字编程
API介绍
DatagramSocket
DatagramSocket是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket构造方法:
UDP数据报套接字方法表
方法签名 | 方法说明 |
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
DatagramSocket方法
数据报套接字方法表
方法签名 | 方法说明 |
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket构造方法:
DatagramPacket方法说明表
方法签名 | 方法说明 |
DatagramPacket(byte[]buf,int length) | 构造一个DatagramPacket用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[]buf,int offset,int length,SocketAddress address) | 构造一个DatagramPacket用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号 |
DatagramPacket方法:
网络编程方法签名及说明表
方法签名 | 方法说明 |
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
这个对象本质上就是包装了字节数组。
网络通信程序的基本流程[通用的流程]
这个模型为一问一答模型,其实还有其他的模型,因为对于服务器来说,通常是要给多个客户端提供服务的。
接下来写一个最简单的网络程序。
服务器
1. 从客户端读取到请求内容
2. 根据请求计算响应
3. 把响应返回给客户端
客户端
1. 从控制台读取用户输入的内容
2. 通过网络发送给服务器
3. 从服务器读取到响应
4. 把响应结果显示到控制台上 不同的服务器,根据请求,计算出来的响应是不一样的,具体的根据业务逻辑去实现。
此处就做一个简单的“回显服务器”(echo server) 省略了根据请求计算响应部分,而是把请求直接作为响应进行返回。
关注要点:
1. socket api的使用
2. 客户端服务器的工作流程
网络通信中关于套接字(socket)绑定的端口号谁是源端口,谁是目的端口?需要具体情况具体分析。
我们已经知道了网络通信五元组为: 源ip,源端口,目的ip,目的端口,协议类型
而服务器在启动的时候,绑定一个端口号(假设9090,咱们可以随意指定的)
客户端在启动的时候,绑定一个端口号(假设1234)
客户端给服务器发的请求, 源端口就是1234,目的端口就是9090
服务器给客户端返回的响应 源端口就是9090,目的端口就是1234
我们需要指定的是“空闲端口”
一个端口同一时刻,只能被一个进程(socket) 绑定
假设,微信和QQ音乐绑定了同一个端口,此时网卡收到了这个端口的数据,要交给哪个程序来处理呢?操作系统是不支持这个操作的。
如果在绑定端口的时候,该端口已经被其他进程占用了,此时绑定就会失败,也就是抛出异常。
端口号在网络协议中,是使用 2 个字节表示的无符号整数, 范围为0 - 65535
而0-1024 这个范围的端口,称为“知名端口号” 。这些端口号被系统预留了,给一些知名的协议的服务器来进行使用的。
我们自己写代码最好避开这个范围。
那么怎么知道哪些端口被占用了? 又如何知道,当前系统中,正在运行的程序都使用了哪些端口呢?
此时有两种方法供我们选择:
1) 尝试绑定一下,成功就ok,不成功就换个。
2) 通过命令查看端口的情况. windows 中通过 netstat 命令查看 netstat -ano
*通过观察可知,tcp更多。其实tcp和udp都可能更多。 取决于电脑上都装了哪些程序, 但是事实上,tcp确实比udp功能更强,大部分程序使用tcp协议,所以tcp更多也是可以理解的。
*端口号是2个字节但是用int接收, 它会检查我们输入的大小吗?系统的原生API不会检查,但是如果设置的比较大,会直接截断。 Java的API 确实是检查了:
由于服务器要给多个客户端提供服务,并且每个客户端都可能发起多个请求。 所以我们要使用while循环,来持续不断的处理各种客户端的各种请求。 死循环,不见得是bug。尤其是对于服务器,主逻辑往往就是一个死循环。
其中 p是一个输出型参数
编写 udp 版本的 echo server
服务器一启动,就会执行到 receive.
如果此时还没有客户端,发送任何请求,receive 就会阻塞。
其中responseString.getBytes().length是传入字节数组的长度,以字节为单位。
构造这个 DatagramPacket 的时候,需要拿着 String 里面的字节数组来进行构造。
千万不能写作response.length ();因为这个是以字符为单位
进行send的时候,要把这个packet发给谁呢? 一个服务器,对应多个客户端
原则上,这个请求是谁发的,我就返回给谁。
而 UDP是“无连接”,也就是说UDP socket自身,里面不保存对方的信息的
此时就需要把 发给谁这样的信息,放到“packet”中
一次通信,涉及到一个“五元组”
源IP,源端口 目的IP,目的端口 协议类型
上次谈到的“封装”“分用”
服务器收到的客户端的请求,不是光一个UDP数据报
UDP外面有IP IP外面有以太网数据帧……
request.getSocketAddress()这个方法,拿到了这个数据包对应的客户端的 IP和端口。
调用receive操作收到的其实是一个完整的这样的“数据”。虽然以太网报头和IP的报头,都被操作系统解析了。 但是仍然可以通过DatagramPacket 类获取到其中的关键信息。
把这里的客户端中的ip和端口拿出来,用来构造响应数据报
这里的代码可以体现UDP的几个特点:
1.UDP无连接
一上来就可以直接receive /send
没有建立连接的代码
2.面向数据报
3.全双工
*一些区别,request.getSocketAddress拿到ip和端口的整体,而reqPacket.getAddress只获取ip。
package network.UDP;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class EchoServer {//创建socket对象private DatagramSocket socket;//创建构造方法,初始化socket对象public EchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}//启动服务器,完成主要的业务逻辑public void start() throws IOException{System.out.println("服务器启动!");while (true) {//1.读取请求并进行解析//(1)创建一个空白DatagramPacket对象DatagramPacket request = new DatagramPacket(new byte[4096], 4096);//(2)通过receive方法读取网卡的数据,如果网卡没有收到数据,就会阻塞socket.receive(request);//(3)解析数据,得到请求的内容String requestString = new String(request.getData(), 0, request.getLength());//2.根据请求计算响应String responseString = process(requestString);//3.把响应写回客户端//(1)创建一个DatagramPacket对象,封装响应内容DatagramPacket response = new DatagramPacket(responseString.getBytes(), responseString.getBytes().length,request.getSocketAddress());//(2)把DatagramPacket对象通过send方法发送给客户端socket.send(response);//4.打印日志System.out.printf("[%s:%d] req: %s; resp: %s\n", request.getAddress().toString(), request.getPort(), requestString, responseString);}}//根据请求内容,返回响应内容//由于我们是“回显服务器”,所以响应内容就是请求内容private String process(String requestString) {return requestString;}public static void main(String[] args) throws IOException {EchoServer echoServer = new EchoServer(9090);echoServer.start(); }
}
编写 udp 版本 的 echo client
客户端的构造方法,需要填写服务器的ip和端口。
服务器则不需要,只需要指定自己的端口。
作为服务器,发数据的时候,就可以通过收到的请求,直到是要发给谁。
作为客户端,主动发起的一方,必须得实现知道服务器在哪里。(程序员手动指定的)
客户端在创建socket对象的时候,不需要指定端口号, 操作系统会自动分配一个空闲的端口
客户端这里指定也可以,但是通常不会这么做, 因为一个端口,同一时刻,只能被一个进程绑定。(第二个进程也尝试绑定,就会出错)
对于服务器来说,由于服务器是在程序员自己手里。如果出现端口冲突了,程序员很容易就能处理
而 对于客户端来说,是在用户自己的电脑上的。(用户电脑上可能会安装很多奇奇怪怪的程序,占用各种奇奇怪怪的端口,程序员管不着,也不能管) 所以,如果客户端里指定固定端口,就很可能会和客户端电脑上的其他程序的端口冲突。因此通常不需要指定端口号。
package network.UDP;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class EchoClient {private DatagramSocket socket = null; //创建socket对象,用于发送和接收数据报private String serverIp; //服务器的IP地址private int serverPort; //服务器的端口号public EchoClient(String serverIp, int serverPort) throws SocketException { //构造方法,初始化socket对象this.serverIp = serverIp; //初始化服务器IP地址this.serverPort = serverPort; //初始化服务器端口号socket = new DatagramSocket(); //创建DatagramSocket对象,用于发送和接收数据报}public void start() throws IOException { Scanner scanner = new Scanner(System.in); //创建Scanner对象,用于读取用户输入System.out.println("客户端启动!"); while (true) { System.out.println("->"); String requestString = scanner.nextLine(); DatagramPacket request = new DatagramPacket(requestString.getBytes(), requestString.getBytes().length,InetAddress.getByName(serverIp),serverPort); socket.send(request); //发送数据报DatagramPacket response = new DatagramPacket(new byte[4096], 4096); //创建DatagramPacket对象,用于接收数据报socket.receive(response); //接收数据报String responseString = new String(response.getData(), 0, response.getLength()); //解析数据报,得到响应内容System.out.println(responseString); //打印响应内容}}public static void main(String[] args) throws IOException {EchoClient client = new EchoClient("127.0.0.1", 9090);client.start();}}
环回IP 英文名叫loopback 表示自己这个主机。 当服务器和客户端在同一个主机上的时候 无论主机真实的ip是啥,都可以通过127.0.0.1来访问服务器。
如果启动出现以下错误,说明端口已经被占用了
编写 udp 版本的字典客户端和字典服务器
package network.UDP;import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;public class DictServer extends EchoServer{private Map<String,String> dict = new HashMap<>();public DictServer(int port) throws SocketException {super(port);dict.put("cat", "猫");dict.put("dog", "狗");dict.put("pig", "猪");dict.put("cow", "牛");dict.put("sheep", "羊");dict.put("chicken", "鸡");dict.put("duck", "鸭");dict.put("goat", "羊");dict.put("horse", "马");dict.put("elephant", "大象");dict.put("monkey", "猴");dict.put("tiger", "老虎");dict.put("lion", "狮子");dict.put("giraffe", "长颈鹿");dict.put("zebra", "斑马");dict.put("bear", "熊");dict.put("fox", "狐狸");dict.put("wolf", "狼");dict.put("deer", "鹿");dict.put("rabbit", "兔子");dict.put("mouse", "老鼠");dict.put("hamster", "仓鼠");dict.put("fish", "鱼");}@Overridepublic String process(String requestString) {return dict.getOrDefault(requestString, "未找到该单词");}public static void main(String[] args) throws IOException {DictServer dictServer = new DictServer(9090);dictServer.start();}}
package network.UDP;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class EchoClient {private DatagramSocket socket = null; //创建socket对象,用于发送和接收数据报private String serverIp; //服务器的IP地址private int serverPort; //服务器的端口号public EchoClient(String serverIp, int serverPort) throws SocketException { //构造方法,初始化socket对象this.serverIp = serverIp; //初始化服务器IP地址this.serverPort = serverPort; //初始化服务器端口号socket = new DatagramSocket(); //创建DatagramSocket对象,用于发送和接收数据报}public void start() throws IOException { Scanner scanner = new Scanner(System.in); //创建Scanner对象,用于读取用户输入System.out.println("客户端启动!"); while (true) { System.out.println("->"); String requestString = scanner.next(); DatagramPacket request = new DatagramPacket(requestString.getBytes(), requestString.getBytes().length,InetAddress.getByName(serverIp),serverPort); socket.send(request); //发送数据报DatagramPacket response = new DatagramPacket(new byte[4096], 4096); //创建DatagramPacket对象,用于接收数据报socket.receive(response); //接收数据报String responseString = new String(response.getData(), 0, response.getLength()); //解析数据报,得到响应内容System.out.println(responseString); //打印响应内容}}public static void main(String[] args) throws IOException {EchoClient client = new EchoClient("127.0.0.1", 9090);client.start();}}
相关文章:

【Java ee初阶】网络编程 UDP socket
网络编程 socket api 是传输层提供的api。 UDP 无连接,不可靠传输,面向数据报,全双工。 TCP 有链接,可靠传输,面向字节流,全双工。 UDP socket api 数据报 DatagrammSocket 代表了操作系统中的socket文…...

旅游推荐数据分析可视化系统算法
旅游推荐数据分析可视化系统算法 本文档详细介绍了旅游推荐数据分析可视化系统中使用的各种算法,包括推荐算法、数据分析算法和可视化算法。 目录 推荐算法 基于用户的协同过滤推荐基于浏览历史的推荐主题推荐算法 亲子游推荐算法文化游推荐算法自然风光推荐算法…...

c语言第一个小游戏:贪吃蛇小游戏08(贪吃蛇完结)
贪吃蛇撞墙和想不开咬死自己 #include <curses.h> #include <stdlib.h> struct snake{ int hang; int lie; struct snake *next; }; struct snake food; struct snake *head; struct snake *tail; int key; int dir; #define UP 1 #define DOWN -1 …...

使用PhpStudy搭建Web测试服务器
一、安装PhpStudy 从以下目录下载PhpStudy安装文件 Windows版phpstudy下载 - 小皮面板(phpstudy) (xp.cn) 安装成功之后打开如下界面 点击启动Apache 查看网站地址 在浏览器中输入localhost:88,出现如下页面就ok了 二、与Unity交互 1.配置下载文件路径,点击…...

c语言第一个小游戏:贪吃蛇小游戏06
实现贪吃蛇四方向的风骚走位 实现代码 #include <curses.h> #include <stdlib.h> struct snake{ int hang; int lie; struct snake *next; }; struct snake *head; struct snake *tail; int key; int dir; //全局变量 #define UP 1 //这个是宏定义&a…...

Qt应用程序启动时的一些思路:从单实例到性能优化的处理方案
程序启动时优化的价值 在桌面软件开发领域,应用程序的启动过程就像音乐的序曲,决定了用户对软件品质的第一印象。比如首次启动等待超过3秒时,会让大多数用户产生负面看法,而专业工具软件的容忍阈值甚至更低。Qt框架作为跨平台开发…...
【前端三剑客】Ajax技术实现前端开发
目录 一、原生AJAX 1.1AJAX 简介 1.2XML 简介 1.3AJAX 的特点 1.3.1AJAX 的优点 1.3.2AJAX 的缺点 1.4AJAX 的使用 1.4.1核心对象 1.4.2使用步骤 1.4.3解决IE 缓存问题 1.4.4AJAX 请求状态 二、jQuery 中的AJAX 2.1 get 请求 2.2 post 请求 三、跨域 3.1同源策略…...

一文详解Spring Boot如何配置日志
一、写在前面 对于日志文件,相信大家都并不陌生,通过在关键位置打印相关的日志,有利于快速跟踪和定位软件系统运行中存在的问题。 在之前的 Java 实现日志记录的文章中,我们介绍了能实现日志记录的主流框架有 Log4j、Log4j2、Lo…...

Springboot | 如何上传文件
文章目录 1. 核心上传逻辑:FileUploadController2. 使文件系统中的文件可通过 HTTP 访问:WebConfig3. 安全性配置:WebSecurityConfig4. 前端实现(这里用的是Angular) 在许多应用程序开发中,我们经常需要实现…...
axios结合AbortController取消文件上传
<template><div><input type"file" multiple change"handleFileUpload" /><button click"cancelUpload" :disabled"!isUploading">取消上传</button><div>总进度:{{ totalProgress }}…...

spring中的@Async注解详解
一、核心功能与作用 Async 是Spring框架提供的异步方法执行注解,用于将方法标记为异步任务,使其在独立线程中执行,从而提升应用的响应速度和吞吐量。其主要作用包括: 非阻塞调用:主线程调用被标记方法后立即返回&…...

MyBatis 报错:Column count doesn‘t match value count at row 1 详解与解决
本文适用于使用 MyBatis MySQL 开发中出现 “Column count doesnt match value count at row 1” 报错的朋友,尤其是在批量插入或更新数据时,遇到 XML 映射文件中 insert 标签报错的问题。 一、遇到的问题: 二、错误原因分析 列数与值数量不…...
第四天——贪心算法——种花
1. 题目 有一个花坛,其中0 表示该位置是空的,可以种花。1 表示该位置已经有花,不能种花。 规则:新种的花不能种在相邻的位置(即如果某个位置已经种了花,它的左右两个相邻位置不能再种花)。给定…...

【人工智能】自然语言编程革命:腾讯云CodeBuddy实战5步搭建客户管理系统,效率飙升90%
CodeBuddy 导读一、产品介绍1.1 **什么是腾讯云代码助手?**1.2 插件安装1.2.1 IDE版本要求1.2.2 注意事项1.2.4 插件安装1.2.4.1 环境安装1.2.4.2 安装腾讯云AI代码助手** 1.2.5 功能介绍1.2.5.1 Craft(智能代码生成)1.2.5.2 Chat(…...

麦肯锡110页PPT企业组织效能提升调研与诊断分析指南
“战略清晰、团队拼命、资源充足,但业绩就是卡在瓶颈期上不去……”这是许多中国企业面临的真实困境。表面看似健康的企业,往往隐藏着“组织亚健康”问题——跨部门扯皮、人才流失、决策迟缓、市场反应滞后……麦肯锡最新研究揭示:组织健康度…...

【MySQL】第二弹——MySQL表的增删改查(CRUD)初阶
文章目录 🎓一. CRUD🎓二. 新增(Create)🎓三. 查询(Rertieve)📖1. 全列查询📖2. 指定列查询📖3. 查询带有表达式📖4. 起别名查询(as )📖 5. 去重查询(distinct)📖6. 排序…...
内存、磁盘、CPU区别,Hadoop/Spark与哪个联系密切
1. 内存、磁盘、CPU的区别和作用 1.1 内存(Memory) 作用: 内存是计算机的短期存储器,用于存储正在运行的程序和数据。它的访问速度非常快,比磁盘快几个数量级。在分布式计算中,内存用于缓存中间结果、存储…...
hz2新建Keyword页面
新建一个single-keywords.php即可,需要筛选项再建taxonomy-knowledge-category.php 参考:https://www.tkwlkj.com/customize-wordpress-category-pages.html WordPress中使用了ACF创建了自定义产品分类products,现在想实现自定义产品分类下的…...

离散制造企业WMS+MES+QMS+条码管理系统高保真原型全解析
在离散型制造企业的生产过程中,库存管理混乱、生产进度不透明、质检流程繁琐等问题常常成为制约企业发展的瓶颈。为了帮助企业实现全流程数字化管控,我们精心打造了一款基于离散型制造企业(涵盖单件生产、批量生产、混合生产模式)…...
【并发编程基石】CAS无锁算法详解:原理、实现与应用场景
一、什么是CAS? CAS(Compare-And-Swap) 是现代并发编程的核心算法之一,它通过处理器指令级的原子操作实现线程安全,无需传统锁机制。其核心逻辑可以用一个公式表示: CAS(V, E, N) {if (V E) { // 比较当…...
(自用)Java学习-5.8(总结,springboot)
一、MySQL 数据库 表关系 一对一、一对多、多对多关系设计外键约束与级联操作 DML 操作 INSERT INTO table VALUES(...) DELETE FROM table WHERE... UPDATE table SET colval WHERE...DQL 查询 基础查询:SELECT * FROM table WHERE...聚合函数:COUNT()…...
GOOSE 协议中MAC配置
在 GOOSE(Generic Object Oriented Substation Event)协议中,主站(Publisher)发送的 MAC 地址不需要与从站(Listener)的 MAC 地址一致,其通信机制与 MAC 地址的匹配逻辑取决于 GOOSE…...
机器学习之决策树与决策森林:机器学习中的强大工具
机器学习之决策树与决策森林:机器学习中的强大工具 摘要:本文深入探讨决策树和决策森林在机器学习中的应用优势及其适用场景。决策树凭借其易于配置、原生处理多种数据类型、鲁棒性及可解释性等特点,在小数据集和表格数据处理方面表现卓越。…...
【Redis】谈谈Redis的设计
Redis(Remote Dictionary Service)是一个高性能的内存键值数据库,其设计核心是速度、简单性和灵活性。以下从架构、数据结构、持久化、网络模型等方面解析 Redis 的设计实现原理: 1. 核心设计思想 内存优先:数据主要存…...
【C++】流(Stream)详解:标准流、文件流和字符串流
【C】流(Stream)详解:标准流、文件流和字符串流 在C编程中,流(Stream)是一个非常重要的概念,它为我们提供了统一的数据输入输出接口。本文将详细介绍C中的三种主要流类型:标准流、文件流和字符串流。 一、标准流(Standard Strea…...

基于 Spring Boot 瑞吉外卖系统开发(十三)
基于 Spring Boot 瑞吉外卖系统开发(十三) 查询套餐 在查询套餐信息时包含套餐的分类名,分类名称在category表中,因此这里需要进行两表关联查询。 自定义SQL如下: select s.* ,c.name as category_name from setmeal…...

POSE识别 神经网络
Pose 识别模型介绍 Pose 识别是计算机视觉领域的一个重要研究方向,其目标是从图像或视频中检测出人体的关键点位置,从而估计出人体的姿态。这项技术在许多领域都有广泛的应用,如动作捕捉、人机交互、体育分析、安防监控等。 Pose 识别模型的…...
CSS3 基础知识、原理及与CSS的区别
CSS3 基础知识、原理及与CSS的区别 CSS3 基础知识 CSS3 是 Cascading Style Sheets 的第3个版本,是CSS技术的升级版本,于1999年开始制订,2001年5月23日W3C完成了CSS3的工作草案。 CSS3 主要模块 选择器:更强大的元素选择方式盒…...
电能质量扰动信号信号通过hilbert变换得到瞬时频率
利用Hilbert变换从电能质量扰动信号中提取瞬时频率、瞬时幅值、Hilbert谱和边际谱的详细步骤及MATLAB代码实现。该流程适用于电压暂降、暂升、谐波、闪变等扰动分析。 1. Hilbert变换与特征提取流程 1.1 基本步骤 信号预处理:滤波去噪(如小波去噪&…...
Linux工作台文件操作命令全流程解析(高级篇之awk精讲)
全文目录 1 工具介绍2 核心优势3 命令格式3.1 命令格式说明3.2 组成部分详解3.2.1 选项3.2.2 模式3.2.3 动作3.2.4 输入文件 4 使用说明4.1 常用示例4.2 awk 编程解析4.2.1 基础说明4.2.2 编程进阶 4.3 温馨提示 5 内置变量6 参考文献 写在前面 前面一篇《Linux工作台文件操作命…...