# Linux原理与应用课程作业 **Repository Path**: Lucheng_Fu/linux ## Basic Information - **Project Name**: Linux原理与应用课程作业 - **Description**: 里面附有代码、makefile、md文档和结果截图(在img文件夹中),其中每一次作业有一个单独的md文档用于说明每一次作业,README文档中将每次实验的说明文档整合 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-11-27 - **Last Updated**: 2023-12-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 武汉大学 Linux 基本原理与应用 2023 # 小组分工: 伏麓丞(2021302111021):task1+task3+task6+task7的服务端+1、3、6、7任务的实验报告 邓天瑞(2021302111020):task2+task4+task5+task7的客户端+2、4、5、7任务的实验报告 # 开发环境准备 ## **1. 开发环境说明** ### **1.1 openEuler版本** 本次所使用的openEuler版本为openEuler-22.03-LTS-SP2-x86\_64-dvd,这里还建立了可视化界面,具体如图所示。 ![](img/1/openEuler.jpeg?v=1\&type=image) ### **1.2 C开发套件版本** 所使用的gcc版本是10.3.1 ![](img/1/gcc.jpeg?v=1\&type=image)所使用的gdb版本为11.1 ![](img/1/gdb.jpeg?v=1\&type=image) ### **1.3 make版本** 所使用的make工具版本为4.3 ![](img/1/make.jpeg?v=1\&type=image) ### **1.4 git版本** 所使用的git工具版本为2.33.0 ![](img/1/git.jpeg?v=1\&type=image) 使用步骤如下: `git init` `git add .` `git commit -m "描述信息"` `git remote add origin 远程仓库地址` `git push -u origin 分支名` ## **2. git托管库地址** # Hello, openEuler! ## **1. 抓取网络包脚本 (**`catch.sh`) ### **1.1 主要功能和思路介绍** 该脚本用于抓取指定网络接口的数据包,并提取其中的 IP 地址信息,将提取的 IP 地址保存到指定的文件中。 #### 主要功能: 1. **指定网络接口:** 通过变量 `INTERFACE` 指定抓包的网络接口,这里为 `ens33`。 2. **保存 IP 地址:** 使用 `tcpdump` 命令捕获网络包,通过管道传递给 `grep` 命令,使用正则表达式提取其中的 IP 地址,并将结果保存到指定文件 (`ip_addresses.txt`)。 ### **1.2 实验结果** 运行catch.sh后可以抓取对应网络接口的网络包,这里的网络接口是ens33。 ![](img/2/catch.png?v=1\&type=image) 抓取的ip结果保存在ip\_addresses.txt文件中 ## ![](img/2/ip.png?v=1\&type=image) ## **2. Hello 程序 (**`hello.c`) ### **2.1 主要功能和思路介绍** 这个简单的 C 程序用于输出 "Hello, openEuler!"。 #### 主要功能: 1. 使用 `printf` 函数输出 "Hello, openEuler!"。 ### **2.2 实验结果** 通过 `make` 命令完成编译后,运行可以输出hello,openEuler。 ![](img/2/hello.png?v=1\&type=image) ## **3. Makefile** ### **3.1 主要功能和思路介绍** Makefile 用于编译 `hello.c` 程序,生成可执行文件,并包含一个用于运行程序的目标,以及一个用于执行网络包抓取脚本的目标。 #### 主要功能: 1. **编译程序:** 使用 `gcc` 编译 `hello.c` 文件,生成名为 `hello` 的可执行文件。 2. **运行程序:** 提供一个目标 `run`,通过 `make run` 命令运行生成的可执行文件。 3. **执行网络包抓取脚本:** 提供一个目标 `catch`,通过 `make catch` 命令执行网络包抓取脚本。 4. **清理编译生成的文件:** 提供一个目标 `clean`,通过 `make clean` 命令删除生成的可执行文件。 ## **4. 实验总结** 通过这次实验,我学会了使用脚本抓取网络包中的 IP 地址信息,并将其保存到文件中。同时,了解了如何通过 Makefile 管理程序的编译、运行和清理过程,提高了项目的可维护性。这些工具和技术在日常开发中都是非常有用的。 # 进程列表 ## **1. 主要功能和思路介绍** ### **1.1 主要功能** 该程序是一个简单的进程管理工具,具有两个主要功能: 1. **显示所有进程信息:** 程序可以列出系统中所有运行中进程的PID和名称。 2. **终止指定进程:** 用户可以通过提供进程的PID作为命令行参数,终止特定的进程。 3. **进程信息排序:** 列出的进程信息按照PID进行了排序,以提高可读性。 4. **错误处理:** 通过检查目录和文件打开操作的返回值,以及使用perror函数来处理错误,确保程序能够适当地处理可能发生的错误情况。 ### **1.2 设计思路** #### 显示所有进程信息: 1. 通过访问Linux系统中的/proc目录,该目录包含有关运行中进程的信息。 2. 定义了struct ProcessInfo结构体,用于存储每个进程的PID和名称。 3. compareProcesses函数用于比较两个ProcessInfo结构体,以便在输出时按照进程的PID进行排序。 4. printAllProcesses函数打开/proc目录,读取每个进程的status文件,从中提取进程名称和PID,并将这些信息存储在ProcessInfo结构体数组中。最后,对数组按照PID进行排序,并将结果输出到控制台。 5. killProcess函数使用kill系统调用发送SIGTERM信号来终止指定PID的进程。 6. main函数根据命令行参数的数量,选择性地调用printAllProcesses或killProcess函数。如果没有参数,就列出所有进程;如果有一个参数,就尝试终止指定PID的进程。 ## **2. 主要数据结构** struct ProcessInfo 结构体: 用途:存储每个进程的相关信息,包括进程的PID(Process ID)和名称。 成员:int pid:进程的PID。 char name\[256]:进程的名称,最大长度为256个字符。 ```c struct ProcessInfo { int pid; char name[MAX_NAME_LENGTH]; }; ``` MAX\_PROCESSES 和 MAX\_NAME\_LENGTH 宏定义: 用途:定义了两个宏,分别表示ProcessInfo结构体数组的最大长度和进程名称的最大长度。 MAX\_PROCESSES:最大进程数,限制了进程数组的大小,防止数组越界。 MAX\_NAME\_LENGTH:最大进程名称长度,用于定义name数组的长度。 ```c #define MAX_PROCESSES 256 #define MAX_NAME_LENGTH 256 ``` 局部数组和缓冲区: struct ProcessInfo processes\[MAX\_PROCESSES]:用于存储进程信息的数组。限制了数组的大小,防止过多的进程导致栈溢出。 char procStatusPath\[256]:用于存储进程状态文件路径的缓冲区,限制了路径的最大长度。 ## **3. 主要算法** \####3.1 compareProcesses 函数 算法: 用于在调用 qsort 函数时比较两个 ProcessInfo 结构体,以便对进程数组进行排序。 实现: 返回两个结构体的PID之差。 ```c int compareProcesses(const void *a, const void *b) { return ((struct ProcessInfo *)a)->pid - ((struct ProcessInfo *)b)->pid; } ``` \####3.2 printAllProcesses 函数: 算法: 遍历 /proc 目录下的文件,获取每个进程的 PID 和名称,并存储在 ProcessInfo 结构体数组中。然后,使用 qsort 对数组按照 PID 进行排序,最后输出排序后的进程信息。 实现: 打开 /proc 目录,遍历其中的文件。 对每个进程,打开其 status 文件,提取名称和 PID。 将提取到的信息存储在 ProcessInfo 结构体数组中。 ```c FILE *statusFile = fopen(procStatusPath, "r"); if (statusFile != NULL) { char line[MAX_NAME_LENGTH]; while (fgets(line, sizeof(line), statusFile)) { if (sscanf(line, "Name: %s", processes[processCount].name) == 1) { processes[processCount].pid = pid; processCount++; break; } } fclose(statusFile); ``` 关闭文件,关闭目录,然后使用 qsort 对数组进行排序。 ```c qsort(processes, processCount, sizeof(struct ProcessInfo), compareProcesses); ``` 输出排序后的进程信息。 #### 3.3 killProcess 函数: 算法: 使用 kill 系统调用发送 SIGTERM 信号以终止指定PID的进程。 实现: 调用 kill 函数,如果成功则输出终止成功的消息,否则输出错误信息。 ```c void killProcess(int pid) { if (kill(pid, SIGTERM) == 0) { printf("Process %d successfully terminated.\n", pid); } else { perror("kill"); } } ``` #### 3.4 主函数 算法: 根据命令行参数的数量,选择性地调用 printAllProcesses 或 killProcess 函数。 实现: 如果没有参数,调用 printAllProcesses 列出所有进程。 如果有一个参数,将参数转换为整数并调用 killProcess 终止指定PID的进程。 如果参数数量不符合要求,输出使用说明并退出程序。 ## **4. 实验结果** 首先可以输出所有的进程: ![](img/3/输出所有进程.png?v=1\&type=image) 之后指定某一进程的pid之后可以kill指定进程: ![](img/3/kill.png?v=1\&type=image) ## **5. 实验总结** ### **5.1 问题以及解决办法** * **问题:** 无法打开/proc目录或读取进程状态文件。 * **解决办法:** 确保程序以足够的权限运行。 在打开文件和目录之前,进行错误检查并使用perror输出错误信息,以便更好地调试。 * **问题:** kill系统调用失败 * **解决办法**:在调用kill之后,检查返回值并处理错误情况。使用perror输出详细的错误信息。 ### **5.2 总结与体会** 通过本次实验,我理解了 Linux 系统中通过读取 `/proc` 目录下的文件获取进程信息的原理。理解和正确使用系统调用是开发系统级应用的关键。在这个项目中,我学到了如何使用kill系统调用来终止进程,并且意识到对错误处理的精细处理是确保程序鲁棒性的关键。在实现过程中,通过对目录的遍历和文件的读取,我进一步熟悉了 C 语言中文件和目录操作的相关函数。 # 内存分配 ## **1. 主要功能和思路介绍** ### **1.1 主要功能** 1. 该程序实现了一个简单的自定义初始化内存池:使用 initializeMemory 初始化内存池,准备好一块初始内存块。 2. 分配内存:使用 umalloc 函数分配内存,根据需要的大小在空闲内存块链表中找到合适的块,进行分割和可能的新块创建。 3. 释放内存:使用 ufree 函数释放内存,将释放的内存块添加到空闲内存块链表中。 4. 合并相邻内存块:使用 umerge 函数在释放内存后合并相邻的空闲内存块,减少碎片化。 5. 打印内存表:使用 printMemoryTable 函数在每个重要步骤后打印当前的内存表,以便查看内存分配和释放的情况。 ### **1.2 设计思路** #### 初始化内存: 使用结构体 MemoryBlock 表示内存块,包含大小和下一个块的指针。内存池由一个字符数组 mem 组成,用于存储所有内存块的数据。空闲内存块链表通过指针 freeList 管理。初始化时,将整个内存池视为一个初始的空闲块。 #### 分配内存 (umalloc): 遍历自由内存块链表,找到第一个大小大于等于请求大小的内存块。 如果找到合适的内存块,根据请求大小切割出一个新的内存块,并更新自由内存块链表。 返回分配的内存块的地址。 #### 释放内存 (ufree): 根据传入的指针找到对应的内存块。 将该内存块添加到自由内存块链表的头部。 #### 合并空闲内存块 (umerge): 遍历自由内存块链表,查找相邻的空闲内存块。如果找到相邻的空闲内存块,将其合并为一个大的空闲内存块。 #### 打印内存表: printMemoryTable 函数:打印当前的内存状态表格,包括每个空闲内存块的地址和大小。 ## 2. 主要数据结构 ##### 2.1内存块结构体 struct MemoryBlock 结构体: 用途:表示内存块的结构体,包含该块的大小和指向下一个内存块的指针。 成员: size\_t size:存储内存块的大小。 struct MemoryBlock \*next:指向下一个内存块的指针。 ```c struct MemoryBlock { size_t size; struct MemoryBlock* next; }; ``` char mem\[MEMORY\_SIZE] 数组: 用途:模拟内存池,作为整个内存管理系统的存储空间。 大小:MEMORY\_SIZE 定义了内存池的总大小。 ```c #define MEMORY_SIZE 8096 char mem[MEMORY_SIZE]; ``` struct MemoryBlock \*freeList 指针: 用途:指向空闲内存块链表的头部,维护了空闲内存块的可用状态。 初始值:在初始化阶段,该指针被设置为初始内存块。 ```c struct MemoryBlock* freeList = NULL; ``` #### 2.2数据结构关系: 在空闲内存块链表中,每个 struct MemoryBlock 结构体通过 next 指针链接到下一个内存块,形成一个链表。 这个链表用于维护可用的、未分配的内存块。 mem 数组充当了整个内存池的角色,是一个连续的字节序列,包含了所有内存块的数据。 初始内存块的头部即是 struct MemoryBlock 结构体,用于存储初始内存块的大小和链表指针。 ## **3. 主要算法** \##3. 主要算法 ###3.1 初始化算法 (initializeMemory 函数): 功能: 在内存池中创建一个初始的内存块,并将其添加到空闲内存块链表中。 实现: 使用 struct MemoryBlock 结构体表示初始内存块,设置其大小为整个内存池大小减去结构体大小,然后将其添加到空闲内存块链表中。 ```c void initializeMemory() { struct MemoryBlock* initialBlock = (struct MemoryBlock*)mem; initialBlock->size = MEMORY_SIZE - sizeof(struct MemoryBlock); initialBlock->next = NULL; freeList = initialBlock; } ``` #### 3.2 分配算法 (umalloc 函数): 功能: 分配指定大小的内存块。 实现: 遍历空闲内存块链表,找到第一个大小满足需求的内存块。如果该内存块的大小超过请求大小,将其分割成两个块,一个用于分配,另一个保留在链表中。 ```c void* umalloc(size_t requestedSize) { //... if (remainingSize > sizeof(struct MemoryBlock)) { struct MemoryBlock* newBlock = (struct MemoryBlock*)((char*)currentBlock + sizeof(struct MemoryBlock) + requestedSize); newBlock->size = remainingSize - sizeof(struct MemoryBlock); newBlock->next = currentBlock->next; currentBlock->size = requestedSize; currentBlock->next = newBlock; } if (prevBlock == NULL) { freeList = currentBlock->next; } else { prevBlock->next = currentBlock->next; } //... } ``` #### 3.3 释放算法 (ufree 函数): 功能: 释放先前分配的内存块,将其添加到空闲内存块链表中。 实现: 将释放的内存块添加到空闲内存块链表的头部。 ```c void ufree(void* ptr) { if (ptr == NULL) { return; } struct MemoryBlock* block = (struct MemoryBlock*)((char*)ptr - sizeof(struct MemoryBlock)); block->next = freeList; freeList = block; } ``` #### 3.4 相邻内存块合并算法 (umerge 函数): 功能: 在释放内存块后,合并相邻的空闲内存块。 实现: 遍历空闲内存块链表,检查相邻的内存块是否可以合并。如果是,则合并两个相邻的内存块。 ```c void umerge() { struct MemoryBlock* prevBlock = NULL; struct MemoryBlock* currentBlock = freeList; while (currentBlock != NULL) { struct MemoryBlock* nextBlock = currentBlock->next; if (nextBlock != NULL && (char*)currentBlock + sizeof(struct MemoryBlock) + currentBlock->size == (char*)nextBlock) { currentBlock->size += sizeof(struct MemoryBlock) + nextBlock->size; currentBlock->next = nextBlock->next; } prevBlock = currentBlock; currentBlock = currentBlock->next; } } ``` #### 3.5 打印内存表算法 (printMemoryTable 函数): 功能: 打印当前空闲内存块链表的状态,显示地址和大小。 实现: 通过格式化输出,使用表格形式显示内存块的地址和大小。 ```c void printMemoryTable() { struct MemoryBlock* currentBlock = freeList; printf("| %-16s | %-8s |\n", "Address", "Size"); printf("|------------------|----------|\n"); while (currentBlock != NULL) { printf("| %-16p | %-8zu |\n", (void*)currentBlock, currentBlock->size); currentBlock = currentBlock->next; } printf("\n"); } ``` ## **4. 实验结果** 可以分配指定大小的内存块,并且合并空闲的内存块 ![](img/4/4.png?v=1\&type=image) ## **5. 实验总结** ### **5.1 问题以及解决办法** * **问题:** 内存越界访问问题 * \*\*解决办法:\*\*使用合适的数据结构:确保结构体定义和指针操作不会导致越界。仔细计算指针偏移:在进行指针运算时,确保不会超过内存块的范围。 * **问题:** 合并算法错误问题 * **解决办法:** 仔细检查合并算法:确保合并算法逻辑正确,考虑各种边界条件。添加调试输出:在合并算法中添加打印语句,观察合并操作的执行情况。 ### **5.2 总结与体会** 通过实现一个简单的内存分配器,深入理解了内存分配的基本原理。了解了如何维护空闲内存块链表,如何分割和合并内存块,以及如何利用结构体来表示内存块的信息。 通过在空闲内存块链表中进行插入和删除操作,加深了对链表的理解。链表的设计在内存分配器中起到了关键的作用,能够高效地管理不同大小的内存块。在实现过程中,我遇到了一些问题,例如内存不足、空指针等,通过解决这些问题,我对内存管理有了更深入的理解。 # 文件系统元数据读取 ## **1. 主要功能和思路介绍** ### **1.1 主要功能** 1. 打开设备文件:使用 open 函数打开 ext4 文件系统的设备文件(在代码中是 /dev/vda,需根据实际情况替换为正确的设备文件路径)。 2. 读取超级块数据:使用 readSuperblock 函数从设备文件中读取 ext4 文件系统的超级块数据。超级块包含了有关文件系统整体信息的字段,如 inode 数量、块数量和第一个数据块。 3. 读取组描述符表数据:使用 readGroupDescriptor 函数从设备文件中读取 ext4 文件系统的组描述符表项数据。 4. 读取 inode 表项数据:使用 readInode 函数从设备文件中读取 ext4 文件系统的 inode 表项数据。inode 表项包含了文件或目录的元数据信息,如文件类型和大小。 5. 格式化打印解析结果:使用 printf 函数按照一定格式输出解析得到的超级块、组描述符表项和 inode 结构的详细信息。关闭设备文件:使用 close 函数关闭设备文件。 ### **1.2 设计思路** #### 结构体定义 使用三个结构体 ext4\_superblock、ext4\_group\_desc、ext4\_inode 分别表示 ext4 文件系统的超级块、组描述符表项和 inode 结构。 #### 读取超级块: 使用 `open` 函数打开指定设备文件。使用 `lseek` 将文件指针定位到超级块的位置。 使用 `read` 函数读取超级块的信息,并存储在 `struct ext4_superblock` 结构中。 #### 读取组描述符: 使用 `lseek` 将文件指针定位到组描述符的位置,计算偏移量为 `firstDataBlock * 1024 + sizeof(struct ext4_superblock)`。使用 `read` 函数读取组描述符的信息,并存储在 `struct ext4_group_desc` 结构中。 #### 读取 inode 结构: 使用 `lseek` 将文件指针定位到 inode 表的位置,计算偏移量为 `inodeTableBlock * 1024`。使用 `read` 函数读取 inode 的信息,并存储在 `struct ext4_inode` 结构中。 #### 输出信息: 输出超级块、组描述符和 inode 结构的相关信息到标准输出。 ## **2. 主要数据结构** #### 2.1 struct ext4\_superblock 结构体: 用途:表示 ext4 文件系统的超级块,包含了有关文件系统整体信息的字段。 成员: uint32\_t s\_inodes\_count:文件系统中的 inode 总数。 uint32\_t s\_blocks\_count:文件系统中的块总数。 uint32\_t s\_first\_data\_block:第一个数据块的块号。 ```c struct ext4_superblock { uint32_t s_inodes_count; uint32_t s_blocks_count; uint32_t s_first_data_block; }; ``` #### 2.2 struct ext4\_group\_desc 结构体: 用途:表示 ext4 文件系统的组描述符表项,包含了与文件系统组相关的信息。 成员: uint32\_t bg\_block\_bitmap:块位图的块号。 uint32\_t bg\_inode\_table:inode 表的块号。 ```c struct ext4_group_desc { uint32_t bg_block_bitmap; uint32_t bg_inode_table; }; ``` #### 2.3struct ext4\_inode 结构体: 用途:表示 ext4 文件系统的 inode 结构,包含了文件或目录的元数据信息。 成员: uint16\_t i\_mode:文件类型和访问权限。 uint32\_t i\_size:文件的大小。 ```c struct ext4_inode { uint16_t i_mode; uint32_t i_size; }; ``` ## **3. 主要算法** ### **3.1 读取超级块:** 功能: 从设备文件中读取 ext4 文件系统的超级块数据。 算法: 使用 lseek 函数将文件指针定位到超级块的位置,然后使用 read 函数读取超级块数据。系统提供的功能。 ```c void readSuperblock(int fd, struct ext4_superblock *sb) { lseek(fd, 1024, SEEK_SET); read(fd, sb, sizeof(struct ext4_superblock)); } ``` ### **3.2 读取组描述符:** 功能: 从设备文件中读取 ext4 文件系统的组描述符表项数据。 算法: 使用 lseek 函数将文件指针定位到组描述符表的位置,然后使用 read 函数读取组描述符表项数据。 ```c void readGroupDescriptor(int fd, struct ext4_group_desc *gd, uint32_t firstDataBlock) { lseek(fd, firstDataBlock * 1024 + sizeof(struct ext4_superblock), SEEK_SET); read(fd, gd, sizeof(struct ext4_group_desc)); } ``` ### **3.3 读取 inode 结构:** 功能: 从设备文件中读取 ext4 文件系统的 inode 表项数据。 算法: 使用 lseek 函数将文件指针定位到 inode 表的位置,然后使用 read 函数读取 inode 表项数据。 ```c void readInode(int fd, struct ext4_inode *inode, uint32_t inodeTableBlock) { lseek(fd, inodeTableBlock * 1024, SEEK_SET); read(fd, inode, sizeof(struct ext4_inode)); } ``` ## **4. 实验结果** 如图所示,可以进行文件系统超级块、组描述符表、位图解析、inode 表项解析 ![](img/5/5.png?v=1\&type=image) ## **5. 实验总结** ### **5.1 问题以及解决办法** * **问题:** 文件读取失败问题 * **解决办法:** 在 read 函数后添加错误检查,检查返回值确定是否成功读取文件内容。使用 perror 输出具体的错误信息,有助于理解读取失败的原因。 * **问题:** 代码中的一些操作,如文件读取、内存拷贝,没有考虑到边界情况,可能导致缓冲区溢出等问题。 * **解决办法:** 使用安全的函数,如 snprintf、memcpy\_s 等,来代替可能不安全的函数。对于输入数据进行边界检查,确保不会导致缓冲区溢出。 ### **5.2 总结与体会** 在本次实验中,我通过解析 ext4 文件系统的超级块、组描述符和 inode 结构,深入理解了文件系统的基本结构和元数据存储方式。 了解了文件系统是如何组织和管理存储空间的,包括超级块、组描述符、块位图、inode 表等各个层次的概念。虽然这只是一个简单的文件系统解析程序,但它揭示了实际文件系统的一部分结构。了解这些概念对于深入研究和理解操作系统和文件系统非常重要。在实现过程中,遇到了一些文件读写和结构体成员理解的问题,通过查阅资料和错误信息,逐步解决了这些问题。这次实验增強了我对文件系统和底层数据结构的理解。 # 挂载 webdav 网盘 ### 1.主要功能和思路介绍 #### 1.1 主要功能 通过解析WebDAV URL提取信息,并根据用户指定的操作类型执行挂载或卸载操作 #### 1.2 设计思路 该程序是一个用于挂载和卸载WebDAV存储的命令行工具。通过解析命令行参数,提取WebDAV URL中的用户名、密码、远程地址和端口信息。然后根据用户指定的操作(mount或unmount),执行相应的挂载或卸载操作。在挂载时,程序会检查本地挂载点是否存在,若不存在则创建,并使用系统命令sudo mount.davfs执行挂载操作。在卸载时,程序会使用sudo umount执行卸载操作,并在成功卸载后删除本地挂载点。程序还包含错误处理,例如无法创建目录、挂载失败或卸载失败时会输出相应的错误信息并退出程序。 ### 2.第三方模块 #### 2.1 davfs2 `davfs2`(DAV FileSystem 2)是一个用于在Linux系统上挂载WebDAV(Web-based Distributed Authoring and Versioning)存储的文件系统驱动程序。WebDAV是一种基于HTTP/HTTPS协议的扩展,允许用户通过Web访问和管理文件,支持文件的创建、修改、删除等操作。 `davfs2` 的主要特点和功能包括: 1. **WebDAV协议支持:** 支持WebDAV协议,通过HTTP/HTTPS与远程服务器通信,允许用户像本地文件系统一样访问和管理远程的WebDAV存储。 2. **挂载WebDAV存储:** 允许用户将远程的WebDAV存储挂载到本地的文件系统,使得用户可以通过标准文件I/O操作进行读写。 3. **用户权限管理:** 提供了用户名和密码的配置选项,以便在挂载时进行身份验证,确保只有经过授权的用户可以访问和操作WebDAV存储。 4. **缓存支持:** 具有缓存机制,可以提高对远程WebDAV存储的访问性能,减少不必要的网络传输。 5. **透明性:** 用户无需了解底层的网络细节,`davfs2`会负责处理与WebDAV服务器的通信,使得对用户而言,访问远程文件系统与访问本地文件系统没有显著差异。 6. **集成到文件系统层:** `davfs2`将WebDAV存储集成到Linux文件系统层,使得用户可以使用标准的文件和目录命令,如`ls`、`cp`、`mv`等,对WebDAV存储进行操作。 #### 2.2 葫芦儿·派盘  葫芦App是一款提供安全永久存储的个人云服务产品。葫芦为用户提供超大容量云存储空间,通过网盘功能云备份手机照片、视频、文件等重要资料。独有蓝光存储技术,告别传统硬盘,保护数据永不丢失。 ### 3.主要数据结构 #### **3.1 字符数组** * `char username[MAX_URL_LENGTH]`: 用于存储从WebDAV URL中提取的用户名。 * `char password[MAX_URL_LENGTH]`: 用于存储从WebDAV URL中提取的密码。 * `char remote_address[MAX_URL_LENGTH]`: 用于存储从WebDAV URL中提取的远程地址。 * `char mount_command[MAX_URL_LENGTH + 100]`: 用于构建挂载命令的字符串。 * `char umount_command[MAX_URL_LENGTH + 50]`: 用于构建卸载命令的字符串。 #### 3.2 整数 * `int port`: 用于存储从WebDAV URL中提取的端口信息。 * `int argc`: 用于存储命令行参数的数量。 #### 3.3 常数 * `#define MAX_URL_LENGTH 256`: 定义了WebDAV URL的最大长度。 ### 4.主要算法 #### **4.1 WebDAV URL解析算法:** * 使用`sscanf`函数从WebDAV URL中提取用户名、密码、远程地址和端口信息,将其存储在相应的字符数组和整数变量中。 ```C void extract_webdav_info(const char* webdav_url, char* username, char* password, char* remote_address, int* port) { sscanf(webdav_url, "%[^:]:%[^@]@%[^:]:%d", username, password, remote_address, port); } ``` #### **4.2 命令行参数处理算法:** * 使用`argc`和`argv`来获取用户在命令行中输入的操作类型、本地挂载点路径和WebDAV URL。 #### **4.3 挂载操作算法:** * 判断本地挂载点是否存在,若不存在则使用`mkdir`函数创建。 * 构建挂载命令字符串,并使用`system`函数执行该命令。 ```C snprintf(mount_command, sizeof(mount_command), "sudo mount.davfs -o Username=%s,Password=%s,port=%d %s %s", username, password, port, remote_address, local_mount_point); if (system(mount_command) == 0) { printf("WebDAV successfully mounted to %s\n", local_mount_point); } else { fprintf(stderr, "Mount failed\n"); exit(EXIT_FAILURE); } ``` #### **4.4 卸载操作算法:** * 构建卸载命令字符串,并使用`system`函数执行该命令。 * 在卸载成功后,尝试使用`rmdir`函数删除本地挂载点。 ```C snprintf(umount_command, sizeof(umount_command), "sudo umount %s", local_mount_point); if (system(umount_command) == 0) { printf("WebDAV successfully unmounted from %s\n", local_mount_point); // 删除本地挂载点 if (rmdir(local_mount_point) != 0) { perror("Unable to delete directory"); exit(EXIT_FAILURE); } } ``` #### **4.5 错误处理算法:** * 使用`perror`函数输出系统错误信息。 * 使用`fprintf`函数输出自定义的错误信息。 ### 5.实验结果 首先要在网盘进行webdav的配置 ![](img/6/开启webdav.png?v=1\&type=image) 在挂载之后,可以发现,已经成功挂载了网盘里的内容(本地挂载点为task6里面的mount\_files,一开始为空): ![](img/6/挂载.png?v=1\&type=image) 在卸载之后,可以看到已经成功卸载: ![](img/6/卸载.jpg?v=1\&type=image) ### 6.实验总结 #### 6.1 问题以及解决办法 * **问题:** davfs2在openEuler无法直接安装 * **解决办法:** 首先从网上下载davfs2的压缩包:wget -P ./configure make make install groupadd davfs2 usermod -a -G davfs2 \$(id -un) useradd davfs2 -g davfs2 * **问题:** davfs2在普通的configure之后make会出错 * **解决办法:** ./configure --prefix\=${libdir} \ CFLAGS="$CXXFLAGS -fPIC" \ \--enable-static\=yes #### 6.2 总结与体会 通过本次实验,我学习了如何通过命令行工具挂载和卸载 WebDAV 存储,以及如何处理相关的用户身份验证、错误情况等。同时,了解了使用 `davfs2` 这样的第三方模块来简化 WebDAV 操作的步骤。 # Let meChat! ## **1. 主要功能和思路介绍** ### **1.1 主要功能** 通过Socket实现基于TCP协议的通信服务端和客户端,实现简单的聊天功能。服务端接受客户端连接,进行简单的用户认证,然后双方可以进行文字聊天。 ### **1.2 设计思路** #### 服务端: 1. 创建服务器套接字,绑定到指定端口并开始监听连接。 2. 接受客户端连接请求,进行用户认证,这里的验证只需要使用。 3. 认证成功后,进入聊天循环,接收客户端消息并回复。 #### 客户端: 1. 创建客户端套接字,连接到服务器。 2. 提供用户名和密码,发送给服务器进行认证。 3. 认证成功后,进入聊天循环,发送消息给服务器并接收服务器的回复。 ## **2. 主要数据结构** ### **2.1 服务端:** * `struct sockaddr_in server_addr`: 存储服务器的地址信息。 * `struct sockaddr_in client_addr`: 存储客户端的地址信息。 * `int server_socket`: 服务器套接字,用于监听连接。 * `int client_socket`: 客户端套接字,用于与客户端通信。 * `char auth_message[BUFFER_SIZE]`: 存储客户端发送的认证信息。 * `char client_message[BUFFER_SIZE]`: 存储客户端发送的聊天消息。 ### **2.2 客户端:** * `char user[50], password[50], server_ip[50]`: 存储用户从命令行参数中提取的用户名、密码、服务器IP地址。 * `int server_port`: 存储用户从命令行参数中提取的服务器端口号。 * `struct sockaddr_in server_address`: 存储服务器的地址信息。 ## **3. 主要算法** ### **3.1 服务端:** 1. **接受连接算法:** * 使用`socket`、`bind`、`listen`、`accept`等函数建立服务器套接字,并接受客户端连接。 2. **认证算法:** * 使用`read`函数接收客户端发送的用户名和密码进行认证,然后使用`write`函数向客户端发送认证结果。 3. **聊天循环算法:** * 使用无限循环,通过`read`接收客户端发送的消息,然后使用`printf`打印消息,并使用`fgets`获取服务器用户的回复,最后使用`write`发送回复至客户端。 ### **3.2 客户端:** 1. **连接服务器算法:** * 使用`socket`函数创建客户端套接字,并使用`connect`函数连接到服务器。 2. **认证算法:** * 使用`write`函数发送用户名和密码进行认证,然后使用`read`函数接收服务器返回的认证结果。 3. **聊天循环算法:** * 使用无限循环,通过`fgets`获取用户输入的消息,然后使用`write`发送消息至服务器,最后使用`read`接收服务器返回的消息并打印。 ## **4. 实验结果** 如图所示,客户端和服务端均可以进行相互发送消息: ![](img/7/chat.png?v=1\&type=image) ## **5. 实验总结** ### **5.1 问题以及解决办法** * **问题:** 一开始对于服务器的ip地址无法确定 * **解决办法:** 发现服务器和本机的ip地址是一样的,因此只需要使用本机的ip地址即可 ### **5.2 总结与体会** 通过本次实验,我学习了如何通过命令行工具实现基于 TCP 协议的简单聊天应用。在设计中,服务端和客户端之间的通信采用 Socket 技术,用户认证部分简化为用户名和密码的传递。这次实验拓展了我的网络编程知识,对于理解 Socket 编程和基本的用户认证机制有很好的帮助。