Linux数据管理初探

Linux数据管理初探

  • 导语
  • 内存管理
    • 内存分配
    • 内存错用和处理
  • 文件锁定
    • 锁文件/区域锁
    • 读写和竞争
    • 锁命令和死锁
  • dbm数据库
    • 例程
    • dbm访问函数
    • 其他dbm函数
  • 总结
  • 参考文献

导语

Linux为应用程序提供简洁的视图用来反映可直接寻址的内存空间(但实际上可能是内存+外存),并且Linux提供了内存保护机制(如遇到访问越界自动终止等),当Linux被正确配置后,它提供给用户逻辑上的空间会比实际的空间更大(例如交换-覆盖技术和虚拟存储器技术)

内存管理

内存分配

最基础的内存分配是通过C语言的malloc实现的(C语言基础内容,不多解释),它返回一个void指针,使用的时候需要强转,并且malloc保证返回的地址是内存对齐的

由于例如交换-覆盖和虚拟存储器这类技术的存在,Linux程序在运行时往往可以获得更多比机器物理内存容量更多的内存,书上给出了一个例子,由于笔者机器内存是4GB,因此修改了相关参数,具体代码和运行结果如下

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main()
{int *locate=0;for(int i=0;i<2048;i++)//总共申请了8GB{locate=malloc(1024*1024*sizeof(int));//每次申请4MBif(locate)printf("%d times success",i);elseexit(EXIT_FAILURE);}return 0;	
}

在这里插入图片描述

可以看到一直到第2048次申请仍然是有效的,这充分说明了Linux程序是可以申请到比内存更大的空间的

书上给出了一个更极端的例子,这里只给出解析和代码,由于考虑到可能对虚拟机有影响,因此不运行

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main()
{int* locate=0;int obtain=0;while(1){for(int i=0;i<1024;i++){locate=malloc(1024*sizeof(int));//每次4KBif(!locate)exit(EXIT_FAILURE);}//每次4MBobtained++;printf("%d times malloc\n",obtained);}return 0;
}

在预想中,该程序的运行会花费很多时间,并且在分配内存大小接近机器物理内存容量,分配的速度会慢,这是因为程序在不同阶段使用的内存是不一样的(空闲物理内存->交换系统),它们是用请求分页虚拟存储系统实现的,具体相关知识可以参考操作系统的考研课

除了malloc之外,还有一些其他的与内存相关的函数,原型如下

void free(void *ptr_to_memory)//释放指定的内存空间
void *calloc(size_t number_of_elements, size_t element_size);
//为结构数组分配内存,默认初始化0
void *realloc(void *existing_memory, size_t new_size);
//改变已经分配好的内存长度

内存错用和处理

在具体的内存分配使用过程中,通常会出现越界和写/读空这样两种错误,例如下面两个例子

//越界
int *p=malloc(1024);
while(1)
{*p=0;p++;
}
//写/读空
char *p=0;
printf("%s",p);
sprintf(p,"write");

第一个例子,循环会到达超过初始分配空间的地址,Linux的内存管理系统会检测到越界行为并终止程序运行,第二个例子,程序段尝试对一个空指针进行读写,同样会被检测到不能读写而终止

文件锁定

程序经常会需要对数据的权限进行开放或限制,这通常由文件来实现,Linux提供对文件“上锁”和“解锁”的原子操作,而且还可以对文件的某一部分进行上锁或解锁,进行更细微的操作,这里可以参考操作系统中文件互斥和信号量相关的知识点

锁文件/区域锁

锁文件只是充当一个指示器,程序间需要相互协作,锁文件只是建议锁而非强制锁,强制锁会对读写权限进行控制,下面是书上给出的一个例子和运行结果

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main()
{int flag=open("/tmp/test",O_RDWR | O_CREAT | O_EXCL,0444);if(flag!=-1)printf("Succeed\n");//创建并上锁成功else printf("Open failed with error %d\n",errno);//输出错误代码return 0;
}

在这里插入图片描述

可以看到,第一次运行时由于文件不存在,open调用成功,第二次则返回错误码17代表已存在

书上还给出临界区以及进程之间相互竞争临界区的实现,具体代码和结果如下

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
const char *lock_file="/tmp/LCK.test2";//操作的文件
int main()
{int t=10;while(t--){int flag=open(lock_file,O_RDWR | O_CREAT | O_EXCL, 0444);//创建文件if(flag==-1)//如果申请失败代表正在使用{printf("%d - using\n",getpid());//并不是当前进程在使用sleep(3);}else//临界区{printf("%d -access\n",getpid());//当前进程拿到了锁sleep(1);close(flag);//关文件unlink(lock_file);//解锁sleep(2);}}return 0;
}

在这里插入图片描述

可以看到两个进程交互访问临界区,并且当一个进程使用时,另一个进程会等待一会后再次尝试访问临界区

上面的两个例子都是使用文件锁,而不是对文件的某一部分上锁,创建锁文件适用于独占式访问,但是并不适用访问大型的共享文件(例如缓冲区),当出现类似生产者-消费者模型的问题时,锁文件就不适用了,因为一个文件可能有多个区域要被同时更新和访问,如果不及时更新,数据就会丢失,这个时候,区域锁就应运而生

区域锁实现了将文件某个特定部分锁定,然而其他程序可以访问文件中的其他部分(文件段锁定或文件区域锁定),Linux使用fcntl和lockf来实现这一功能(一般用前者,后者是备用的,并且两者由于实现不同不能同时使用),fcntl原型如下

int fcntl(int fildes, int command, ...);
//对打开的文件描述符操作,根据command完成不同任务
int fcntl(int fildes, int command, struct flock *flock_structure);
//command为F_GETLK、F_SETLK、F_SETLKW时的函数时候的fcntl,第三个参数为文件锁
//文件锁结构体具体的成员和取值略,有三种模式,强制共享,解锁,强制私有

F_GETLK获取文件锁信息,不会去尝试锁定文件,在该命令条件下,即使传递了想创建的锁类型,fcntl也会阻止;F_SETLK试图上锁或解锁,对应区域由flock的相关成员变量定义;F_SETLKW是前者的循环版本,当无法获取锁时,调用会等待知道可以(获取锁或收到信号),除了上述的三种,还需要注意的是,程序对某个文件拥有的所有锁都将在相应文件描述符被关闭时自动清除,程序结束时也是

读写和竞争

对文件区域上锁后,就只能用read/write调用而不是fread/fwrite来访问数据,因为后者会对数据进行缓存,实际每次读到的数据会超过目标长度,但如果后面对超过的部分修改,修改的数据并不能及时更新到缓存当中,就会产生读写问题

关于共享锁和私有锁的操作,书上给出了例子,下面是代码和对应的运行结果(先后台运行第一个,再运行第二个)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>const char *test_file = "/tmp/test_lock";int main() {struct flock rg1, rg2;int file = open(test_file, O_RDWR | O_CREAT, 0666);//打开文件if (file < 0) {fprintf(stderr, "fail open\n");return 0;}for (int i = 0; i < 200; i++)//写入数据(void)write(file, "A", 1);rg1.l_type = F_RDLCK;//第一个文件区域rg1.l_whence = SEEK_SET;rg1.l_start = 10;rg1.l_len = 20;rg2.l_type = F_WRLCK;//第二个文件区域rg2.l_whence = SEEK_SET;rg2.l_start = 40;  rg2.l_len = 10;printf("%d locking\n", getpid());printf("Attempting to set rg1 lock...\n");int flag = fcntl(file, F_SETLK, &rg1);//区域1上锁if (flag == -1) {fprintf(stderr, "rg1 lock failed\n");} else {printf("rg1 lock succeeded\n");}printf("Attempting to set rg2 lock...\n");flag = fcntl(file, F_SETLK, &rg2);//区域2上锁if (flag == -1) {fprintf(stderr, "rg2 lock failed\n");} else {printf("rg2 lock succeeded\n");}sleep(60);  // 增加睡眠时间,方便测试printf("%d closing\n", getpid());close(file);return 0;
}

下面是第二个代码


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>const char *test_file = "/tmp/test_lock";void show_lock_info(struct flock *rg) {//展示信息函数printf("\tl_type %d, ", rg->l_type);printf("l_whence %d, ", rg->l_whence);printf("l_start %ld, ", rg->l_start);printf("l_len %ld, ", rg->l_len);printf("l_pid %d\n", rg->l_pid);
}int main() {struct flock rg;int file = open(test_file, O_RDWR | O_CREAT, 0666);if (file < 0) {//如果打不开fprintf(stderr, "fail open\n");return 0;}for (int i = 0; i < 99; i += 5) {//每次测试5个长度rg.l_type = F_WRLCK;rg.l_whence = SEEK_SET;rg.l_start = i;rg.l_len = 5;rg.l_pid = -1;printf("Testing %d to %d, F_WRLCK\n", i, i + 5);int flag = fcntl(file, F_GETLK, &rg);//查看锁信息if (flag == -1) {fprintf(stderr, "F_GETLK failed\n");return 0;}if (rg.l_pid != -1) {printf("lock would fail, F_GETLK returned:\n");show_lock_info(&rg);} else {printf("F_WRLCK would succeed\n");//如果上共享锁会成功}rg.l_type = F_RDLCK;rg.l_whence = SEEK_SET;rg.l_start = i;rg.l_len = 5;rg.l_pid = -1;printf("Testing %d to %d, F_RDLCK\n", i, i + 5);flag = fcntl(file, F_GETLK, &rg);if (flag == -1) {fprintf(stderr, "F_GETLK failed\n");return 0;}if (rg.l_pid != -1) {printf("lock would fail, F_GETLK returned:\n");show_lock_info(&rg);} else {printf("F_RDLCK would succeed\n");//如果上独享锁会成功}}close(file);return 0;
}

运行结果如下,可以看到对10-30上独占锁会失败,对40-50上共享锁会失败

在这里插入图片描述

锁命令和死锁

lockf函数也可以实现对文件的锁定,具体原型如下

int lockf(int fildes, int function, off_t size_to_lock);
//只能操作独占锁,size_to_lock是操作的字节数,从文件当前偏移开始算
/*
F_ULOCK解锁,F_LOCK独占锁
F_TLOCK测试并独占锁,F_TEST测试其他进程设置的锁
*/

lockf和fcntl一样,所有的锁都是建议锁,并不会真正地阻止读取文件中的数据,对锁的检测是程序的责任,但是两者是不能混合使用的

死锁的概念在OS中经常提及,它通常被认为是进程之间资源的分配/申请不当而导致的一种相互等待的状况,书上给出的死锁对象是程序,这当然也可以。当许多用户频繁访问同一个数据时很容易发生死锁,但是Linux内核不能自动解开死锁,这时候就需要一些外部干涉手段,具体可以参考死锁的四个避免原理

dbm数据库

dbm是Linux自带的数据库,它适合存储相对静态的索引化数据库,在使用它之前需要确保当前系统安装了gdbm和ndbm库,并且在编译对应文件时需要加上-lgdbm_compat选项

例程

dbm的基本元素是需要存储的数据块以及与其关联的在检索数据时用作关键字的数据块(键值对),这些通过datum来实现,datum至少包括两个成员变量dptr和dsize,前者是void*类型,它指向数据的起始点,后者是size_t类型,它是包含数据的长度,无论是对待存储或是用来访问它的索引都是用datum实现

dbm访问函数

主要的dbm函数原型和解释如下

DBM *dbm_open(const char *filename, int file_open_flags, mode_t file_mode);
//打开已有数据库或创建数据库,其余参数类似open
int dbm_store(DBM *database ,datum key ,datum content, int store_mode);
//把数据存在数据库中,数据库索引和实际索引,对已有关键字的操作(覆盖或失败)
datum dbm_fetck(DBM *database, datum key);
//检索数据,返回一个datum类型
void dbm_close(DBM *database);
//关闭dbm_open打开的数据库

这里给出一个基于书上代码修改的程序和运行结果,具体解析如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <gdbm.h>
#include <string.h>#define TEST_DB_FILE "/tmp/dbm1_test"typedef struct test_data {//设置的数据char misc_chars[20];int num;char more_chars[20];
} node;node store[3], get;
char key[20];
datum kd, dd;
GDBM_FILE dbm_ptr;int main() {// 打开数据库dbm_ptr = gdbm_open(TEST_DB_FILE, 0, GDBM_WRCREAT, 0666, NULL);if (dbm_ptr == NULL) {perror("gdbm_open");return 1;}// 初始化数据strcpy(store[0].misc_chars, "first!");  // 小写store[0].num = 47;strcpy(store[0].more_chars, "foo");strcpy(store[1].misc_chars, "bar");  // 小写store[1].num = 13;strcpy(store[1].more_chars, "unlucky?");strcpy(store[2].misc_chars, "third");  // 小写store[2].num = 3;strcpy(store[2].more_chars, "baz");// 存储数据到数据库for (int i = 0; i < 3; i++) {sprintf(key, "%c%c%d", store[i].misc_chars[0], store[i].more_chars[0], store[i].num);//生成的关键字为第一个字符串的首字符+第三个字符串首字符+数字kd.dptr = key;kd.dsize = strlen(key);// 分配空间并将结构体数据复制到字符数组char *data_buffer = malloc(sizeof(node));memcpy(data_buffer, &store[i], sizeof(node));dd.dptr = data_buffer;  // 指向字符数组dd.dsize = sizeof(node);// 存储数据,使用替换模式if (gdbm_store(dbm_ptr, kd, dd, GDBM_REPLACE) < 0) {perror("gdbm_store");free(data_buffer); // 释放分配的内存}}// 从数据库中检索数据sprintf(key, "bu%d", 13);  // 使用小写kd.dptr = key;kd.dsize = strlen(key);dd = gdbm_fetch(dbm_ptr, kd);//根据关键字找if (dd.dptr) {printf("Data retrieved\n");memcpy(&get, dd.dptr, dd.dsize);printf("Retrieved item - %s %d %s\n", get.misc_chars, get.num, get.more_chars);free(dd.dptr); // 释放获取的数据} else {printf("No data found for key: %s\n", key);}// 关闭数据库gdbm_close(dbm_ptr);return 0;
}

可以看到成功检索到数据,但需要注意的是,如果先前运行出的结果不对,需要把临时文件删除,不然会影响到第二次运行
在这里插入图片描述

其他dbm函数

除了上一节的函数,还有一些仅供检测状态和删除的函数,函数原型和解释如下

int dbm_delete(DBM *database_descriptor, datum key);
//从数据库删除数据项,成功时返回0
int dbm_error(DBM *database_descriptor);
//测试数据库是否有错误,没有就返回0
int dbm_clearerr(DBM *database_descriptor);
//清除数据库所有已1的错误条件标志
datum dbm_firstkey(DBM *database_descriptor);
datum dbm_nextkey(DBM *database_descriptor);
//类似于C++的迭代器,第一个是获得第一个数据项,第二个是获得下一个位置
/*
for(datum key=dbm_firstkey(db_ptr);key.dptr;key=dbm_nextkey(db_ptr));
*/

基于书上给出的代码修改和运行结果如下(只给出修改部分)

    // 尝试删除数据sprintf(key, "bu%d", 13);  // 使用小写kd.dptr = key;kd.dsize = strlen(key);if (gdbm_delete(dbm_ptr, kd) == 0) {printf("Data with key %s deleted\n", key);} else {printf("Nothing deleted for key %s\n", key);}// 遍历数据库中的所有键并打印数据//这里用了gdbm,原理和dbm一样for (kd = gdbm_firstkey(dbm_ptr); kd.dptr; kd = gdbm_nextkey(dbm_ptr, kd)) {dd = gdbm_fetch(dbm_ptr, kd);if (dd.dptr) {printf("Data retrieved for key: %s\n", (char *)kd.dptr); // 打印当前键memcpy(&get, dd.dptr, dd.dsize);printf("Retrieved item - %s %d %s\n", get.misc_chars, get.num, get.more_chars);free(dd.dptr); // 释放获取的数据} else {printf("No data found for key: %s\n", (char *)kd.dptr);}}// 关闭数据库gdbm_close(dbm_ptr);return 0;

可以看到输入的数据被删除,遍历数据库时只输出了存在的数据

在这里插入图片描述

总结

这一章简述了Linux系统下对文件的各种权限操作以及自带的数据库dbm/gdbm,许多操作系统的经典问题得以用文件锁来实现,一些相对静态的索引化数据也可以用dbm/gdbm来使用

参考文献

  1. 《Linux程序设计(第四版)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/468934.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中4个高效小技巧

分享 4 个省时的 Python 技巧&#xff0c;可以节省 10~20% 的 Python 执行时间。 包含编程资料、学习路线图、源代码、软件安装包等&#xff01;【[点击这里]】&#xff01; 反转列表 Python 中通常有两种反转列表的方法&#xff1a;切片或 reverse() 函数调用。这两种方法都…

【黑马Redis原理篇】Redis数据结构

视频来源&#xff1a;原理篇[2,15] 文章目录 1.动态字符串SDS1.1 内部结构&#xff1a; 2.IntSet3.Dict3.1 dict的内部结构3.2 dict的扩容 4.ziplist压缩列表5.QuickList6.SkipList跳表7.RedisObject对象8.Redis的五种数据结构8.1 String8.2 List8.3 Set8.4 Zset 有序集合8.5 …

WPF之iconfont(字体图标)使用

1&#xff0c;前文&#xff1a; WPF的Xaml是与前端的Html有着高度相似性的标记语言&#xff0c;所以Xaml也可同Html一般轻松使用阿里提供的海量字体图标&#xff0c;从而有效的减少开发工作度。 2&#xff0c;下载字体图标&#xff1a; 登录阿里图标库网iconfont-阿里巴巴矢量…

内网部署web项目,外网访问不了?只有局域网能访问!怎样解决?

相关技术 要实现“内网部署&#xff0c;外网访问”&#xff0c;可以使用内网穿透、VPN技术、DMZ主机、端口映射等方法。以下是对这些方法的详细解释&#xff1a; 一、内网穿透 内网穿透是一种技术&#xff0c;它通过将内网设备映射到公网上的方式&#xff0c;实现外网访问内…

Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)

使用DataBinding&#xff0c;LiveData&#xff0c;Fresco&#xff0c;RecyclerView&#xff0c;Room&#xff0c;ViewModel 完成 玩Android 开放API-玩Android - wanandroid.com 接口使用的是下面的两个&#xff1a; https://www.wanandroid.com/banner/jsonhttps://www.wan…

c++11(一)

c11&#xff08;一&#xff09; 1. C11的发展历史2. 列表初始化2.1 C98传统的{}2.2 C11中的{}2.3 C11中的std::initializer_list 3. 右值引⽤和移动语义3.1 左值和右值3.2 左值引⽤和右值引⽤3.3 引⽤延⻓⽣命周期3.4 左值和右值的参数匹配3.5 右值引⽤和移动语义的使⽤场景3.5…

‍️代码的华尔兹:在 Makefile 的指尖上舞动自动化的诗篇

文章目录 &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️背景——一个优秀工程师必备技能&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️一、&#x1f929;&#x1f929;快速了解…

SpringBoot中使用Thymeleaf模板引擎

和使用freemarker差不多的方式 1、导入thymeleaf的启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 2、编写Controller类 3、编写模板页面 注…

vue之子组件向父组件传值

参考博客先挂上 vue3中子传父&#xff08;emit&#xff09;、父传子&#xff08;props&#xff09;一篇文章拿下第一次写文章&#xff0c;告诉你vue3中如何实现父子相传&#xff0c;一篇文章帮 - 掘金 父组件通过 props 向子组件传值 1.子组件使用 $emit 触发事件 2.在父组件…

第26天 安全开发-PHP应用模板引用Smarty渲染MVC模型数据联动RCE安全

时间轴&#xff1a; 演示案例 新闻列表&模板引用-代码RCE安全 知识点 1、PHP 新闻显示-数据库操作读取显示 2、PHP 模版引用-自写模版&Smarty 渲染 3、PHP 模版安全-RCE 代码执行&三方漏洞 新闻列表 1.数据库创建新闻存储 2.代码连接数据库读取 3.页面进行自定…

【微服务】Docker 容器化

一、初识Docker 1. 为什么需要 Docker 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会遇到一些问题&#xff1a; 依赖关系复杂&#xff0c;容易出现兼容性的问题开发、测试、生产环境有差异 Docker 如何解决依赖的兼容问题 将应用的Libs&#xff08;…

(十四)JavaWeb后端开发——MyBatis

目录 1.MyBatis概述 2.MyBatis简单入门 3.JDBC&#xff08;了解即可&#xff09; 4.数据库连接池​ 5.lombok 6.MyBatis基本操作 7.XML映射文件 8.动态SQL 8.1 if标签 8.2 foreach标签 8.3 sql/include标签​ 1.MyBatis概述 MyBatis是一款优秀的持久层&#xff08…

pytorch实现深度神经网络DNN与卷积神经网络CNN

DNN概述 深度神经网络DNN来自人脑神经元工作的原理&#xff0c;通过在计算机中逻辑抽象出多个节点&#xff0c;接收处理并向后传递信息&#xff0c;实现计算机的自我学习&#xff0c;类比结构见下图&#xff1a; 该方法通过预测输出与实际值的差异不断调整节点参数&#xff0…

私域流量圈层在新消费时代的机遇与挑战:兼论开源 AI 智能名片、2 + 1 链动模式、S2B2C 商城小程序的应用

摘要&#xff1a;本文剖析了私域流量圈层在新消费时代呈现出的独特温度与信任优势&#xff0c;阐述了从传统销售到新消费转型中用户心理的变化。同时&#xff0c;强调了内容对于私域流量的关键作用&#xff0c;并分析开源 AI 智能名片、2 1 链动模式、S2B2C 商城小程序在私域流…

1.4 配置 Android 构建系统

Android 构建系统会编译应用资源和源代码&#xff0c;然后将它们打包成 APK 或 Android App Bundle 文件&#xff0c;供您测试、部署、签名和分发。 创建自定义 build 配置需要您对一个或多个 build 配置文件做出更改。这些纯文本文件使用领域特定语言 (DSL) 通过 Kotlin 脚本&…

containerd配置私有仓库registry

机器ip端口regtisry192.168.0.725000k8s-*-------k8s集群 1、镜像上传 rootadmin:~# docker push 192.168.0.72:5000/nginx:1.26.1-alpine The push refers to repository [192.168.0.72:5000/nginx] 6961f0b8531c: Pushed 3112cd521249: Pushed d3f50ce9b5b5: Pushed 9efaf2eb…

ABAP:SET CURSOR FIELD设置鼠标焦点

SET CURSOR FIELD <字段名>&#xff1a;设置鼠标焦点到该字段 SET CURSOR 设置到鼠标焦点列还是行 SET CURSOR LINE 设置鼠标焦点到行 GET CURSOR field <字段名> &#xff1a;这个相对应的获取鼠标焦点得到的字段

PHP和Python脚本的性能监测方案

目录 1. 说明 2. PHP脚本性能监测方案 2.1 安装xdebug 2.2 配置xdebug.ini 2.3 命令行与VS Code中使用 - 命令行 - VS Code 2.4 QCacheGrind 浏览 3. Python脚本性能监测方案 3.1 命令行 4. 工具 5.参考 1. 说明 获取我们的脚本程序运行时的指标&#xff0c;对分析…

VS code 远程连接到docker容器

1.需要在vscode中下载remote 、docker、dev container插件。 如下图&#xff1a; 有小鲸鱼标志&#xff0c;说明已经成功。 右键可以运行或者停止容器运行

阿里1688 阿里滑块 231滑块 x5sec分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…