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

Netty ByteBuf 使用详解

文章目录

  • 1.概述
  • 2. ByteBuf 分类
  • 3. 代码实例
    • 3.1 常用方法
      • 3.1.1 创建ByteBuf
      • 3.1.2 写入字节
      • 3.1.3 扩容
        • 3.1.2.1 扩容实例
        • 3.1.2.2 扩容计算新容量代码
      • 3.1.4 读取字节
      • 3.1.5 标记回退
      • 3.1.6 slice
      • 3.1.7 duplicate
      • 3.1.8 CompositeByteBuf
      • 3.1.9 retain & release
        • 3.1.9.1 retain & release
        • 3.1.9.2 Netty TailContext release
    • 3.2 完整实例
  • 4. 参考文献

1.概述

ByteBuf 对字节进行操作

ByteBuf 四个基本属性:

  • readerIndex: 读指针,字节数组,读到哪了
  • writerIndex: 写指针,字节数组,写到哪了
  • maxCapacity:最大容量,字节数组最大容量
  • markedReaderIndex:标记读指针,resetReaderIndex方法可以把readerIndex修改为markedReaderIndex,回退重新读数据
  • markedWriterIndex: 标记写指针,resetReaderIndex方法可以把 writerIndex 修改为markedWriterIndex,回退重新写数据
public abstract class AbstractByteBuf extends ByteBuf {int readerIndex;int writerIndex;private int markedReaderIndex;private int markedWriterIndex;private int maxCapacity;
}

2. ByteBuf 分类

ByteBuf 分为

  • 直接内存或堆内存(Heap/Direct)
  • 池化 和 非池化(Pooled/Unpooled)和 操作方式是否安全 (Unsafe/非 Unsafe)

ByteBuf 创建可以基于直接内存或堆内存

  • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放

ByteBuf 池化 和 非池化

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

ByteBuf 操作方式是否安全 (Unsafe/非 Unsafe)

  • Unsafe:表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 的方式操作数据
  • 非 Unsafe:则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标的方式操作数据

3. 代码实例

3.1 常用方法

3.1.1 创建ByteBuf

创建ByteBuf , 默认都是池化的

        // 堆内存的ByteBufByteBuf bufferHeap = ByteBufAllocator.DEFAULT.heapBuffer();// 直接内存的ByteBufByteBuf bufferDirect = ByteBufAllocator.DEFAULT.directBuffer();System.out.println(bufferHeap);System.out.println(bufferDirect);

在这里插入图片描述

3.1.2 写入字节

        bufferHeap.writeBytes(new byte[]{1, 2, 3, 4});bufferDirect.writeBytes(new byte[]{1, 2, 3, 4});print("第一次写入", bufferHeap);print("第一次写入", bufferDirect);

在这里插入图片描述

3.1.3 扩容

3.1.2.1 扩容实例
  • 默认 256
  • 扩容加一倍
  • 到了4194304,每次+4194304
      for (int i = 0; i < 100; i++) {bufferHeap.writeBytes(new byte[]{1, 2, 3, 4});bufferDirect.writeBytes(new byte[]{1, 2, 3, 4});}print("批量写入&扩容", bufferHeap);print("批量写入&扩容", bufferDirect);

在这里插入图片描述

3.1.2.2 扩容计算新容量代码
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {ObjectUtil.checkPositiveOrZero(minNewCapacity, "minNewCapacity");if (minNewCapacity > maxCapacity) {throw new IllegalArgumentException(String.format("minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity));} else {int threshold = 4194304;if (minNewCapacity == 4194304) {return 4194304;} else {int newCapacity;if (minNewCapacity > 4194304) {newCapacity = minNewCapacity / 4194304 * 4194304;if (newCapacity > maxCapacity - 4194304) {newCapacity = maxCapacity;} else {newCapacity += 4194304;}return newCapacity;} else {for(newCapacity = 64; newCapacity < minNewCapacity; newCapacity <<= 1) {}return Math.min(newCapacity, maxCapacity);}}}
}

3.1.4 读取字节

        readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);

在这里插入图片描述

3.1.5 标记回退

        bufferHeap.markReaderIndex();bufferDirect.markReaderIndex();readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);System.out.println("回退");bufferHeap.resetReaderIndex();bufferDirect.resetReaderIndex();readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);

在这里插入图片描述

3.1.6 slice

    // 无参 slice 是从原始 ByteBuf 的 read index 到 write index 之间的内容进行切片// slice 和 bufferHeap 共享一块内存ByteBuf slice = bufferHeap.slice();slice.setByte(0, 9);print("slice", slice);readByte(bufferHeap);

在这里插入图片描述

3.1.7 duplicate

        // 内存拷贝不共享内存ByteBuf duplicate = bufferHeap.duplicate();print("duplicate", duplicate);print("bufferHeap", bufferHeap);duplicate.writeBytes(new byte[]{5});print("duplicate", duplicate);print("bufferHeap", bufferHeap);

在这里插入图片描述

3.1.8 CompositeByteBuf

        // CompositeByteBuf 是一个组合的 ByteBuf,它内部维护了一个 Component 数组,// 每个 Component 管理一个 ByteBuf,记录了这个 ByteBuf 相对于整体偏移量等信息,代表着整体中某一段的数据。// 优点,对外是一个虚拟视图,组合这些 ByteBuf 不会产生内存复制// 缺点,复杂了很多,多次操作会带来性能的损耗ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});CompositeByteBuf buf3 = ByteBufAllocator.DEFAULT.compositeBuffer();// true 表示增加新的 ByteBuf 自动递增 write index, 否则 write index 会始终为 0buf3.addComponents(true, buf1, buf2);print("buf3", buf3);

在这里插入图片描述

3.1.9 retain & release

3.1.9.1 retain & release

Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口

  • 每个 ByteBuf 对象的初始计数为 1
  • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
  • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
  • 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用
        bufferHeap.retain();bufferDirect.retain();bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);

在这里插入图片描述

3.1.9.2 Netty TailContext release

io.netty.channel.DefaultChannelPipeline.TailContext

io.netty.channel.DefaultChannelPipeline.TailContext#channelRead

        @Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {onUnhandledInboundMessage(ctx, msg);}

io.netty.channel.DefaultChannelPipeline#onUnhandledInboundMessage(ChannelHandlerContext, Object)

    protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) {onUnhandledInboundMessage(msg);if (logger.isDebugEnabled()) {logger.debug("Discarded message pipeline : {}. Channel : {}.",ctx.pipeline().names(), ctx.channel());}}

3.2 完整实例


import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;public class ByteBufStudy {public static void main(String[] args) {// 堆内存的ByteBufByteBuf bufferHeap = ByteBufAllocator.DEFAULT.heapBuffer();// 直接内存的ByteBufByteBuf bufferDirect = ByteBufAllocator.DEFAULT.directBuffer();System.out.println(bufferHeap);System.out.println(bufferDirect);bufferHeap.writeBytes(new byte[]{1, 2, 3, 4});bufferDirect.writeBytes(new byte[]{1, 2, 3, 4});print("第一次写入", bufferHeap);print("第一次写入", bufferDirect);for (int i = 0; i < 100; i++) {bufferHeap.writeBytes(new byte[]{1, 2, 3, 4});bufferDirect.writeBytes(new byte[]{1, 2, 3, 4});}print("批量写入&扩容", bufferHeap);print("批量写入&扩容", bufferDirect);readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);bufferHeap.markReaderIndex();bufferDirect.markReaderIndex();readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);System.out.println("回退");bufferHeap.resetReaderIndex();bufferDirect.resetReaderIndex();readByte(bufferHeap);readByte(bufferDirect);print("读取一个字节", bufferHeap);print("读取一个字节", bufferDirect);// 无参 slice 是从原始 ByteBuf 的 read index 到 write index 之间的内容进行切片// slice 和 bufferHeap 共享一块内存ByteBuf slice = bufferHeap.slice();slice.setByte(0, 9);print("slice", slice);readByte(bufferHeap);// 内存拷贝不共享内存ByteBuf duplicate = bufferHeap.duplicate();print("duplicate", duplicate);print("bufferHeap", bufferHeap);duplicate.writeBytes(new byte[]{5});print("duplicate", duplicate);print("bufferHeap", bufferHeap);// CompositeByteBuf 是一个组合的 ByteBuf,它内部维护了一个 Component 数组,// 每个 Component 管理一个 ByteBuf,记录了这个 ByteBuf 相对于整体偏移量等信息,代表着整体中某一段的数据。// 优点,对外是一个虚拟视图,组合这些 ByteBuf 不会产生内存复制// 缺点,复杂了很多,多次操作会带来性能的损耗ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});CompositeByteBuf buf3 = ByteBufAllocator.DEFAULT.compositeBuffer();// true 表示增加新的 ByteBuf 自动递增 write index, 否则 write index 会始终为 0buf3.addComponents(true, buf1, buf2);print("buf3", buf3);bufferHeap.retain();bufferDirect.retain();bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);bufferHeap.release();bufferDirect.release();print("release bufferHeap", bufferHeap);print("release bufferDirect", bufferDirect);}public static void print(String prefix, ByteBuf buffer) {System.out.printf("%s readerIndex : %s writerIndex : %s maxCapacity : %s capacity : %s %n",prefix, buffer.readerIndex(), buffer.writerIndex(), buffer.maxCapacity(), buffer.capacity());}public static void readByte(ByteBuf buffer) {System.out.printf("读取一个字节: %s %n", buffer.readByte());}
}

4. 参考文献

  • 黑马 Netty教程
  • 拉钩教育 Netty课程 若地老师
    在这里插入图片描述

相关文章:

Netty ByteBuf 使用详解

文章目录 1.概述2. ByteBuf 分类3. 代码实例3.1 常用方法3.1.1 创建ByteBuf3.1.2 写入字节3.1.3 扩容3.1.2.1 扩容实例3.1.2.2 扩容计算新容量代码 3.1.4 读取字节3.1.5 标记回退3.1.6 slice3.1.7 duplicate3.1.8 CompositeByteBuf3.1.9 retain & release3.1.9.1 retain &a…...

怎样去掉卷子上的答案并打印

当面对试卷答案的问题时&#xff0c;一个高效而简单的方法是利用图片编辑软件中的“消除笔”功能。这种方法要求我们首先将试卷拍摄成照片&#xff0c;然后利用该功能轻松擦除答案。尽管这一方法可能需要些许时间和耐心&#xff0c;但它确实为我们提供了一个可行的解决途径。 然…...

海思SS928/SD3403开发笔记1——使用串口调试开发板

该板子使用串口可以调试&#xff0c;下面是win11 调试 该板子步骤 1、给板子接入鼠标、键盘、usb转串口 2、下载SecureCRT&#xff0c;并科学使用 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11dIkZVstvHQUhE8uS1YO0Q 提取码&#xff1a;vinv 3、安装c…...

JSON数据操作艺术

在现代Web开发和数据交换场景中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;扮演着至关重要的角色。它以易于阅读的文本形式存储和传输数据对象&#xff0c;而这些对象的核心便是由属性名&#xff08;键&…...

如何验证Rust中的字符串变量在超出作用域时自动释放内存?

讲动人的故事,写懂人的代码 在公司内部的Rust培训课上,讲师贾克强比较了 Rust、Java 和 C++ 三种编程语言在变量越过作用域时自动释放堆内存的不同特性。 Rust 通过所有权系统和借用检查,实现了内存安全和自动管理,从而避免了大部分内存泄漏。Rust 自动管理标准库中数据类…...

55.Python pip install 安装失败的一个情况Requirement already satisfied

1.问题 以前使用Pycharm 社区版开发的一个项目&#xff0c;今天使用PyCharm 专业版打开&#xff0c;原项目的虚拟环境从venv更换为.venv&#xff0c;然后重新安装插件。安装时&#xff0c;提示Requirement already satisfied: qt_material in c:\tools\python37\lib\site-packa…...

Axios进阶

目录 axios实例 axios请求配置 拦截器 请求拦截器 响应拦截器 取消请求 axios不仅仅是简单的用基础请求用法的形式向服务器请求数据&#xff0c;一旦请求的端口与次数变多之后&#xff0c;简单的请求用法会有些许麻烦。所以&#xff0c;axios允许我们进行创建axios实例、ax…...

C++ 丑数

描述 把只包含质因子2、3和5的数称作丑数&#xff08;Ugly Number&#xff09;。例如6、8都是丑数&#xff0c;但14不是&#xff0c;因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第 n个丑数。 数据范围&#xff1a;0≤&#x1d45b;≤20000≤n≤…...

小山菌_代码随想录算法训练营第三十天|122.买卖股票的最佳时机II、55. 跳跃游戏 、45.跳跃游戏II、1005.K次取反后最大化的数组和

122.买卖股票的最佳时机II 文档讲解&#xff1a;代码随想录.买卖股票的最佳时机II 视频讲解&#xff1a;贪心算法也能解决股票问题&#xff01;LeetCode&#xff1a;122.买卖股票最佳时机II 状态&#xff1a;已完成 代码实现 class Solution { public:int maxProfit(vector<…...

SpringMVC系列七: 手动实现SpringMVC底层机制-上

手动实现SpringMVC底层机制 博客的技术栈分析 &#x1f6e0;️具体实现细节总结 &#x1f41f;准备工作&#x1f34d;搭建SpringMVC底层机制开发环境 实现任务阶段一&#x1f34d;开发ZzwDispatcherServlet&#x1f966;说明: 编写ZzwDispatcherServlet充当原生的DispatcherSer…...

嵌入式web 服务器boa的编译和移植

编译环境&#xff1a;虚拟机 ubuntu 18.04 目标开发板&#xff1a;飞凌OKA40i-C开发板&#xff0c; Linux3.10 操作系统 开发板本身已经移植了boa服务器&#xff0c;但是在使用过程中发现POST方法传输大文件时对数据量有限制&#xff0c;超过1M字节就无法传输&#xff0c;这是…...

什么是js?特点是什么?组成部分?

Js是一种直译式脚本语言&#xff0c;一种动态类型&#xff0c;弱类型&#xff0c;基于原型的高级语言。 直译式&#xff1a;js程序运行过程中直接编译成机器语言。 脚本语言&#xff1a;在程序运行过程中逐行进行解释说明&#xff0c;不需要预编译。 动态类型&#xff1a;js…...

Java 面试题:如何保证集合是线程安全的? ConcurrentHashMap 如何实现高效地线程安全?

在多线程编程中&#xff0c;保证集合的线程安全是一个常见而又重要的问题。线程安全意味着多个线程可以同时访问集合而不会导致数据不一致或程序崩溃。在 Java 中&#xff0c;确保集合线程安全的方法有多种&#xff0c;包括使用同步包装类、锁机制以及并发集合类。 最简单的方法…...

打工人的PPT救星来了!用这款AI工具,10秒生成您的专属PPT

今天帮同事解决了一个代码合并的问题。其实问题不复杂&#xff0c;要把1的代码合到2的位置&#xff1a; 这个处理方式其实很简单&#xff0c;使用 “git cherry-pick hash值” 就可以。 同事直接对我赞许有加&#xff0c;不曾想被领导看到了&#xff0c;对我说了一句&#xff…...

GIT 合拼

合拼有多种方式&#xff1a; 1&#xff09;合拼分支&#xff1a; git merge [source-branch] 2&#xff09;合拼提交 &#xff1a; git cherry-pick [commit-hash] 3&#xff09;合拼单个文件&#xff1a; git checkout [source-branch] – [file] 以上合拼&#xff0c;比如将分…...

利用 Python 和 AI 技术制作智能问答机器人

利用 Python 和 AI 技术制作智能问答机器人 引言 在人工智能的浪潮下&#xff0c;智能问答机器人成为了一种非常实用的技术。它们能够处理大量的查询&#xff0c;提供即时的反馈&#xff0c;并且可以通过机器学习技术不断优化自身的性能。本文将介绍如何使用 Python 来开发一…...

electron系列(一)调用dll

用electron的目的&#xff0c;其实很简单。就是web架构要直接使用前端电脑的资源&#xff0c;但是浏览器限制了使用&#xff0c;所以用electron来达到这个目的。其中调用dll是一个非常基本的操作。 安装 ffi-napi 和 ref-napi 包: npm install ffi-napi ref-napi main.js&…...

VUE3实现个人网站模板源码

文章目录 1.设计来源1.1 网站首页页面1.2 个人工具页面1.3 个人日志页面1.4 个人相册页面1.5 给我留言页面 2.效果和源码2.1 动态效果2.2 目录结构 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh 文章地址&#xff1…...

C语言 | Leetcode C语言题解之第162题寻找峰值

题目&#xff1a; 题解&#xff1a; int findPeakElement(int* nums, int numsSize) {int ls_max0;for(int i1;i<numsSize;i){if(nums[ls_max]>nums[i]);else{ls_maxi;}}return ls_max; }...

利用pickle保存和加载对象

使用 pickle.dump 保存下来的文件可以使用 pickle.load 打开和读取。以下是一个示例&#xff0c;展示了如何使用 pickle 模块保存和加载对象&#xff1a; 保存对象 import pickle# 假设有一个对象 obj obj {"key": "value"}# 将对象保存到文件 with ope…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

密码学基础——SM4算法

博客主页&#xff1a;christine-rr-CSDN博客 ​​​​专栏主页&#xff1a;密码学 &#x1f4cc; 【今日更新】&#x1f4cc; 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 ​编辑…...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决

问题&#xff1a; pgsql数据库通过备份数据库文件进行还原时&#xff0c;如果表中有自增序列&#xff0c;还原后可能会出现重复的序列&#xff0c;此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”&#xff0c;…...

前端工具库lodash与lodash-es区别详解

lodash 和 lodash-es 是同一工具库的两个不同版本&#xff0c;核心功能完全一致&#xff0c;主要区别在于模块化格式和优化方式&#xff0c;适合不同的开发环境。以下是详细对比&#xff1a; 1. 模块化格式 lodash 使用 CommonJS 模块格式&#xff08;require/module.exports&a…...

Centos 7 服务器部署多网站

一、准备工作 安装 Apache bash sudo yum install httpd -y sudo systemctl start httpd sudo systemctl enable httpd创建网站目录 假设部署 2 个网站&#xff0c;目录结构如下&#xff1a; bash sudo mkdir -p /var/www/site1/html sudo mkdir -p /var/www/site2/html添加测试…...